Warming up the neural circuits...
By the end of this chapter you will:
Small APIs are easy to read. Growing APIs become fragile when everything lives in one file.
Day-two problems appear quickly:
FastAPI gives three tools to prevent this drift:
If Chapter 2 showed how to build endpoints, this chapter shows how to keep them maintainable at scale.
from fastapi import APIRouter
users_router = APIRouter(prefix="/users", tags=["users"])
@users_router.get("/")
def list_users():
return [{"id": 1, "name": "Ava"}]Compose routers in app bootstrap:
from fastapi import FastAPI
app = FastAPI()
app.include_router(users_router)Routers create natural boundaries for modules, tests, and team ownership.
from fastapi import Depends, FastAPI
app = FastAPI()
def get_current_user() -> dict[str, str]:
return {"id": "u_123", "role": "admin"}
@
from collections.abc import Generator
class FakeSession:
def close(self):
pass
def get_db() -> Generator[FakeSession, None, None]:
db = FakeSession()
try:
yield db
finally:
from fastapi import Depends, HTTPException
def get_current_user() -> dict[str, str]:
return {"id": "u_123", "role": "member"}
def require_admin(user:
import time
from fastapi import Request
@app.middleware("http")
async def timing_middleware(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
elapsed_ms
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PATCH"
Routes should describe behavior. Dependencies should handle shared request context and policy. Middleware should handle truly global concerns.
| Concern | Best home | Reason |
|---|---|---|
| Domain routes | APIRouter module | Keeps API surface navigable |
| Auth and current user | Dependency function | Reusable and testable |
| DB session lifecycle | yield dependency | Reliable setup/cleanup |
| Timing and tracing | Middleware | Applies consistently to all routes |
| CORS policy | Middleware config | Centralized browser access control |
| Mistake | Why it hurts | Better move |
|---|---|---|
| Monolithic route file | Hard to scale and review | Split by bounded domain routers |
| Copy-pasting auth checks | Inconsistent policy enforcement | Centralize in dependencies |
| Manual DB close in every endpoint | Easy resource leaks | Use yield dependency cleanup |
| Unbounded middleware complexity | Debugging and latency overhead | Keep middleware focused and measurable |
allow_origins=["*"] with credentials | Browser security issues | Pin trusted origins explicitly |
Single-file API appModular app with include_router calls/me and /account endpointsSingle dependency used by both routesSession factorySession always closes on success/failureCurrent user + role check403 for non-admin usersApp middleware stackX-Process-Time-Ms response headerBeginner:
"What problem does APIRouter solve?"
It organizes endpoints into modular groups with shared prefixes/tags so apps stay maintainable as they grow.
"Why use Depends instead of calling helper functions directly in each route?"
Dependencies provide standardized request-scoped resolution, composability, and cleaner testing boundaries.
Senior:
"How do you decide whether logic belongs in middleware or dependencies?"
Middleware for global cross-cutting behavior on every request; dependencies for endpoint-scoped context and policy.
"How do you prevent dependency graphs from becoming opaque in large teams?"
Keep dependencies small, named by intent, documented in module boundaries, and covered by direct unit tests.
APIRouter -> structural modularity
Depends -> shared request-scoped context and policy
yield dependencies -> reliable resource cleanup
Middleware -> global wrappers (timing, tracing, CORS)What is the best primary role of APIRouter in FastAPI?
Dependencies are resolved per request and can depend on other dependencies.
yield dependencies make cleanup reliable even if route logic raises errors.
Keep policy logic centralized and testable through dependency functions.
Middleware wraps request handling globally. Registration order affects behavior.
Avoid wildcard origins in authenticated production APIs unless you fully understand the threat model.