Files
2026-06-01 21:42:05 +00:00

121 lines
4.3 KiB
Markdown

# Routes
Keep route handlers thin. They should own HTTP concerns, not become the place where auth, persistence, validation, and business rules all pile up.
## Why this convention exists
FastAPI already gives routes strong boundary mechanics: parsed inputs, dependency injection, response-model shaping, and framework-managed error handling. The clean pattern in both FastAPI examples and production templates is simple:
- declare request inputs in the signature
- accept collaborators from dependencies
- do small boundary checks
- call the next layer
- return a typed response
When handlers grow past that, they become hard to read, hard to reuse, and hard to test.
## The convention
1. Keep HTTP metadata on the router or route decorator.
2. Keep request parsing explicit in the function signature.
3. Use dependencies for shared request wiring.
4. Keep reusable or non-trivial business rules outside the handler.
5. Return explicit response models or well-understood objects that FastAPI can serialize predictably.
## Follow it by default
This fits almost every application route.
"Thin" does *not* mean the route does nothing. A good route can still:
- load a record
- perform obvious 404/403 checks
- map an input model into a stored object
- commit and return a response
The smell is not "the route did work." The smell is "the route became the system's junk drawer."
## Preferred shape
```python
@router.get("/{id}", response_model=ItemPublic)
def read_item(session: SessionDep, current_user: CurrentUser, id: UUID) -> ItemPublic:
item = session.get(Item, id)
if not item:
raise HTTPException(status_code=404, detail="Item not found")
if not current_user.is_superuser and item.owner_id != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
return item
```
Why this works:
- inputs and collaborators are visible in one line
- boundary failures are explicit
- the handler stays readable without inventing extra layers
## Counterexamples
### Handler as a god function
```python
@router.post("/")
def create_item(...):
# parse auth manually
# open DB manually
# validate request shape again
# run business rules
# send email
# publish event
# build custom response dict
```
Bad because every concern is fused into one boundary blob.
### Hidden response shaping
```python
return {
"ok": True,
"item": some_orm_obj.__dict__,
"extra": weird_runtime_state,
}
```
Bad because the API contract becomes informal and drifts easily.
### Same ownership check copied into every handler body
If the wiring is structurally shared, move it toward a dependency or helper instead of repeating it across routes.
## Source signals
### FastAPI
- `docs_src/bigger_applications/app_an_py310/routers/items.py:5-10` puts prefix, tags, shared dependencies, and canned responses on the router instead of inside each handler.
- `docs_src/bigger_applications/app_an_py310/routers/items.py:16-38` keeps the handlers tiny: one returns a collection, one raises a 404 for a missing item, and one raises a 403 for a forbidden update.
- `docs_src/bigger_applications/app_an_py310/main.py:1-18` assembles routers centrally, which is another signal that route modules are boundary slices, not the whole application.
### Full-stack FastAPI Template
- `backend/app/api/routes/items.py:13-45` keeps list handling inside a single readable flow: explicit params, auth-aware filtering, and response-model construction.
- `backend/app/api/routes/items.py:48-58` shows a narrow read-by-id handler with only fetch + permission checks + return.
- `backend/app/api/routes/items.py:61-72` shows that simple persistence orchestration can still live in the route when the flow is straightforward.
- `backend/app/api/main.py:6-14` centralizes router registration instead of scattering feature mounting across the codebase.
### FastAPI Users
- `examples/beanie-oauth/app/app.py:30-57` composes feature routers at app assembly time instead of stuffing auth flows into one giant module.
- `examples/beanie-oauth/app/app.py:60-62` keeps an authenticated endpoint tiny because auth comes from a dependency.
## Bottom line
A good FastAPI route is a clean HTTP boundary:
- explicit inputs
- explicit collaborators
- explicit failures
- explicit outputs
If a handler starts absorbing subsystems, pull those concerns back out.