Course Content
FastAPI Essentials
1 sections · 32 lessons
Explain the concept of routers in FastAPI.
What you need to know
A router works like a small app: you register routes on it with the same decorators, then include it in the real app. Settings on the router apply to all its routes, and include_router can add more.
1from typing import Annotated2from fastapi import APIRouter, Depends, FastAPI, Header, HTTPException34def verify_api_key(x_api_key: Annotated[str | None, Header()] = None):5 if x_api_key != "k-123":6 raise HTTPException(401, "Bad API key")78# app/api/rag.py9rag = APIRouter(prefix="/rag", tags=["rag"], dependencies=[Depends(verify_api_key)])1011@rag.post("/query")12def query(q: str):13 return {"answer": f"Top passages for {q!r}"}1415@rag.post("/documents", status_code=202)16def add_document(url: str):17 return {"queued": url}1819# app/api/health.py20health = APIRouter(tags=["ops"])2122@health.get("/healthz")23def healthz():24 return {"ok": True}2526# app/main.py27app = FastAPI()28app.include_router(rag, prefix="/v1")29app.include_router(health)Real results:
paths in the app -> ['/healthz', '/v1/rag/documents', '/v1/rag/query']POST /v1/rag/query -> 401 (no key: the router's dependency ran) with X-Api-Key: k-123 -> {'answer': "Top passages for 'refund'"}GET /healthz -> {'ok': True} (no key needed)tags in /docs -> ['ops', 'rag']The prefixes stack: /v1 from include_router plus /rag from the router plus /query from the route. The API-key check was written once but protects both RAG routes. Tags group the endpoints in /docs.
A typical layout
app/ main.py # creates app, lifespan, middleware, include_router calls api/ chat.py # router: /chat, streaming endpoints rag.py # router: /rag, protected by API key admin.py # router: /admin, protected by admin role health.py # router: /healthz, /readyz, no auth deps.py # shared dependencies: get_db, current_user, get_model schemas.py # Pydantic modelsRouters can include other routers, so /v1 can be one router that includes chat, rag and admin.
A real-life example
An ed-tech company's AI tutor started as one main.py with 45 routes. Two teams edited it daily and merge conflicts were constant. A new admin endpoint for deleting student data was added without the admin check, because it was copied from a public route.
They split it into five routers. The admin router has dependencies=[Depends(require_role("admin"))], so any route added to that file is protected automatically. When they needed a breaking change to the chat response format, they created a v2 chat router alongside v1, and moved the mobile app over gradually while old app versions kept working. The health router stayed outside all auth so Kubernetes probes never needed a key.
Follow-up questions to expect
- "Does route order still matter in a router?" — Yes. Within a router, fixed paths such as
/models/latestmust come before/models/{model_id}. - "What is the difference between a router and a mounted sub-app?" — A router merges into the main app and shares its docs, middleware and exception handlers.
app.mount("/legacy", other_app)attaches a separate ASGI app with its own docs and middleware. - "How do you add a dependency to only some routes of a router?" — Put it on those routes with
dependencies=[...]on the decorator, or split them into a second router.