Course Content
FastAPI Essentials
1 sections · 32 lessons
How do you define a route in FastAPI?
What you need to know
FastAPI calls a route a path operation: a path (/models/{model_id}) plus an operation, which is the HTTP method (GET, POST, PUT, PATCH, DELETE). You attach a function to the pair with a decorator.
1from fastapi import FastAPI2from pydantic import BaseModel34app = FastAPI()56class Question(BaseModel):7 text: str89@app.get("/models/latest")10def latest_model():11 return {"model": "sentiment-v3"}1213@app.get("/models/{model_id}")14def read_model(model_id: str, verbose: bool = False):15 return {"model": model_id, "verbose": verbose}1617@app.post("/models/{model_id}/answer")18def answer(model_id: str, body: Question, top_k: int = 3):19 return {"model": model_id, "question": body.text, "top_k": top_k}Real responses from TestClient:
GET /models/latest -> {'model': 'sentiment-v3'}GET /models/bert-small?verbose=yes -> {'model': 'bert-small', 'verbose': True}GET /models/bert-small?verbose=maybe -> 422POST /models/bert-small/answer?top_k=5 -> {'model': 'bert-small', 'question': 'refund?', 'top_k': 5}How FastAPI decides where each parameter comes from
| Parameter looks like | FastAPI treats it as | Example |
|---|---|---|
| Name appears in the path string | Path parameter, always required | model_id |
Simple type (int, str, bool) not in the path | Query parameter; optional if it has a default | verbose, top_k |
| A Pydantic model | JSON request body | body: Question |
The types are not decoration. verbose=yes became True, and verbose=maybe was rejected with a 422 before the function ran.
Other things a route declares
- Status code:
@app.post("/documents", status_code=201)for "created". - Response shape: a return type such as
-> Sentiment(orresponse_model=) validates, filters and documents the output. - Grouping: in a real project routes live on an
APIRouterper area (chat, RAG, admin), which is then added to the app.
Why order matters
FastAPI checks routes top to bottom and uses the first match. If /models/{model_id} is declared first, a request to /models/latest matches it with model_id="latest", and the latest_model function is never reached.
A real-life example
A team serves three sentiment models: English, Hindi and Tamil. Clients call GET /models/{model_id} to read a model's metadata. Later someone adds GET /models/latest at the bottom of the file so the mobile app can find the newest model.
In testing it returns 404 Model 'latest' not found, because the request is caught by the dynamic route, which looks up a model literally named "latest". Nothing crashes, so the bug is easy to miss. Moving the fixed route above the dynamic one fixes it. A good habit is one router per resource, with fixed paths listed first.
Follow-up questions to expect
- "What happens if the client uses the wrong method?" — FastAPI returns 405 Method Not Allowed when the path exists but not for that method.
- "When do you use
async defversusdeffor a route?" —async defwhen everything inside is awaitable I/O; plaindefwhen you call blocking libraries, because FastAPI runs it in a threadpool. - "How do you add validation to a path or query parameter?" — Use
Path()orQuery()with limits, for exampletop_k: Annotated[int, Query(ge=1, le=20)] = 3.