Module 2 · FastAPI · Drills
Reading is not knowing. Type every route yourself, run it, and click it in /docs before you reveal the solution. Effortful recall is the point.
main.py. Write it first, then run fastapi dev main.py and test it on the /docs page — fill the parameters, hit Execute, read the response. Only then click “Show solution” to compare. Tick each box as you go; your progress is saved in this browser.
Drill 1 path param
Write a GET /documents/{doc_id} route. Type doc_id as an int and return it as JSON. Then test /documents/abc in /docs and watch FastAPI reject it.
@app.get("/documents/{doc_id}") def get_document(doc_id: int): return {"doc_id": doc_id} # /documents/7 → {"doc_id": 7} # /documents/abc → 422 automatic validation error
Drill 2 Pydantic + POST
Define a Pydantic Document model with title: str and pages: int. Write a POST /documents that takes one and echoes it back.
from pydantic import BaseModel class Document(BaseModel): title: str pages: int @app.post("/documents") def create_document(doc: Document): return doc
In /docs the request body form is generated from the model — POST {"title": "Intro", "pages": 12}.
Drill 3 query param
Write a GET /documents that accepts an optional limit query parameter defaulting to 10, and returns it.
@app.get("/documents") def list_documents(limit: int = 10): return {"limit": limit} # /documents → {"limit": 10} # /documents?limit=5 → {"limit": 5}
Drill 4 Field validation
Add a constraint to the Document model so title can't be empty (min_length=1) and pages must be positive (gt=0). Try posting {"title": "", "pages": 0} in /docs and read the 422.
from pydantic import BaseModel, Field class Document(BaseModel): title: str = Field(..., min_length=1) pages: int = Field(..., gt=0)
The ... means "required". The 422 response names the exact field and rule that failed — you wrote no validation code.
Drill 5 404
Given an in-memory documents = {} dict, write GET /documents/{doc_id} that raises a 404 when the id isn't present, and returns the document otherwise.
from fastapi import HTTPException documents = {} @app.get("/documents/{doc_id}") def get_document(doc_id: int): if doc_id not in documents: raise HTTPException(404, "Document not found") return documents[doc_id]
raise short-circuits the function — nothing after it runs, and the client gets {"detail": "Document not found"} with status 404.
PUT /documents/{doc_id} that replaces an existing document with a new Document body, raises 404 if the id doesn't exist, and returns the updated document. This completes full Create-Read-Update-Delete.
Build · PUT update endpoint
Assume the lesson's documents dict and Document model are already defined. Add the PUT route.
@app.put("/documents/{doc_id}") def update_document(doc_id: int, doc: Document): if doc_id not in documents: raise HTTPException(404, "Document not found") documents[doc_id] = doc return {"id": doc_id, "document": doc}
Note the signature takes both a path param (doc_id) and a body (doc). FastAPI pulls each from the right place — the int from the URL, the model from the JSON body — because of their types. Test it in /docs: create a document, note its id, then PUT a changed body to that id and GET it back to confirm.
Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.
BaseModel do?/docs (and /redoc for the reference view).raise HTTPException(404, "...") — the framework formats the JSON response./documents/{id}), identifies a resource. Query = after ?, filters/paginates it.response_model do?async def for a route?awaits I/O (DB, network, LLM). Plain def for quick synchronous work.Tick each only if you can do it without looking:
Field constraints for a request bodyHTTPException for errors/docs/docs? You have a real API. Next we make it production-shaped — dependencies, routers, middleware, and auth-ready structure: Lesson 2.2 — FastAPI Advanced.