Path Operations

beginner fastapi routing http-methods

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.

Route matching is first-come-first-served
GET /users/me   ←  specific, declare first
GET /users/{user_id}  ←  greedy, declare after

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.