A path operation in FastAPI lingo is just “a route plus an HTTP method.” Things like GET /users/{id} or POST /orders. We register them with decorators on the app instance.
The decorators
One per HTTP verb:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
async def list_items():
return [{"id": 1, "name": "Notebook"}]
@app.post("/items/")
async def create_item():
return {"id": 2, "name": "Pen"}
@app.put("/items/{item_id}")
async def replace_item(item_id: int):
return {"id": item_id, "replaced": True}
@app.patch("/items/{item_id}")
async def update_item(item_id: int):
return {"id": item_id, "patched": True}
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
return {"deleted": item_id}
PUT replaces the full resource. PATCH does a partial update. GET reads. POST creates. DELETE deletes. Standard REST stuff — FastAPI doesn’t invent anything new here, it just makes the decorators dead simple.
Adding metadata for the docs
The decorator accepts a bunch of kwargs that flow straight into the OpenAPI spec:
@app.post(
"/orders/",
status_code=201,
summary="Create a new order",
description="Creates an order for the authenticated user. Returns 201 with the new order ID.",
tags=["orders"],
response_description="The created order",
)
async def create_order():
return {"id": "ord_123", "status": "pending"}
Each of these shows up in /docs:
- summary — short title, shown collapsed.
- description — full Markdown body, shown when expanded.
- tags — group endpoints into sections in Swagger UI.
- status_code — default response code (we still return the body normally).
- response_description — text next to the response example.
Alternatively, we can put the description in the function docstring and FastAPI picks it up:
@app.post("/orders/", tags=["orders"])
async def create_order():
"""
Creates an order for the authenticated user.
Returns 201 with the new order ID.
"""
return {"id": "ord_123"}
Order matters
Routes match in the order they’re declared. If we have a fixed path and a parameterized path that could overlap, the fixed one must come first.
@app.get("/users/me")
async def current_user():
return {"id": "self", "name": "current"}
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"id": user_id}
If we swapped these, /users/me would hit get_user with user_id = "me" and FastAPI would 422 because “me” isn’t an int. Always declare more specific routes first.
Deprecating an endpoint
We can mark a route deprecated without removing it. It still works, but Swagger shows it struck-through.
@app.get("/legacy/items/", deprecated=True)
async def old_list():
return {"warning": "use /items/ instead"}
Multiple methods on one handler
Rare but possible — we can use app.api_route:
@app.api_route("/health", methods=["GET", "HEAD"])
async def health():
return {"status": "ok"}
Routers (preview)
For real apps we don’t dump every route on app directly. We use APIRouter to split routes across files:
from fastapi import APIRouter
router = APIRouter(prefix="/items", tags=["items"])
@router.get("/")
async def list_items():
return []
# In main.py
app.include_router(router)
Same decorators, but they live on the router. We’ll dig into routers in a later note.
The interview takeaway
Path operations are just decorators that register a function for an HTTP method + path. The decorator’s kwargs (tags, summary, status_code, deprecated) feed directly into the OpenAPI doc. Watch route order — specific before parameterized.