Routing System
FastAPI provides a flexible routing system centered around the APIRouter class, which allows you to organize your application into smaller, reusable components. This is particularly useful for large applications where you want to group related endpoints (e.g., users, items, authentication) into separate files.
Organizing Endpoints with APIRouter
The APIRouter class acts as a "mini FastAPI" instance. You can define path operations on it using the same decorators you use with the main FastAPI app.
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.post("/users/", tags=["users"])
async def create_user(username: str):
return {"username": username}
Including Routers in the Application
To make the routes defined in an APIRouter part of your application, you include them in the main FastAPI instance using the include_router method.
from fastapi import FastAPI
# Assume the router above is in a module named 'users'
# from .routers import users
app = FastAPI()
app.include_router(router)
Applying Shared Configuration
When including a router, you can apply configuration that affects all routes within that router, such as path prefixes, tags, and dependencies. This avoids repeating these parameters for every individual path operation.
from fastapi import APIRouter, Depends, FastAPI
async def verify_token():
# Dependency logic here
pass
app = FastAPI()
router = APIRouter()
@router.get("/items/")
async def read_items():
return [{"item_id": "Portal Gun"}]
# Include with prefix, tags, and dependencies
app.include_router(
router,
prefix="/items",
tags=["items"],
dependencies=[Depends(verify_token)],
responses={404: {"description": "Not found"}},
)
In this example, the read_items endpoint will be accessible at /items/items/ (if the router path was /items/) and will require the verify_token dependency.
Nested Routers
FastAPI supports nesting routers by including one APIRouter inside another. This allows for deep hierarchical organization of your API.
from fastapi import APIRouter, FastAPI
app = FastAPI()
parent_router = APIRouter(prefix="/parent")
child_router = APIRouter(prefix="/child")
@child_router.get("/leaf")
async def leaf_node():
return {"message": "I am a leaf"}
# Nesting the child router into the parent
parent_router.include_router(child_router)
# Including the parent into the app
app.include_router(parent_router)
The endpoint leaf_node will be available at /parent/child/leaf.
WebSocket Routing
APIRouter also supports WebSocket endpoints using the @router.websocket decorator. These routes can also accept path parameters and dependencies.
from fastapi import APIRouter, WebSocket
router = APIRouter()
@router.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}, for client: {client_id}")
Advanced Route Customization
FastAPI uses the APIRoute class (a subclass of Starlette's Route) to handle the execution of path operations. It manages Pydantic validation, dependency injection, and response serialization.
You can customize the behavior of all routes in a router by providing a custom route_class to the APIRouter constructor.
import time
from typing import Callable
from fastapi import APIRouter, Request, Response
from fastapi.routing import APIRoute
class TimingRoute(APIRoute):
def get_route_handler(self) -> Callable:
original_handler = super().get_route_handler()
async def custom_handler(request: Request) -> Response:
before = time.time()
response: Response = await original_handler(request)
duration = time.time() - before
response.headers["X-Response-Time"] = str(duration)
return response
return custom_handler
router = APIRouter(route_class=TimingRoute)
Implementation Details and Gotchas
Docstring Truncation
FastAPI uses the docstring of your endpoint function as the description in the generated OpenAPI schema. If you want to include internal documentation in the docstring that should not appear in the public API docs, use the form feed character (\f). FastAPI truncates the description at the first occurrence of \f.
@router.get("/items/")
async def read_items():
\"\"\"
Read items from the database.
This part is visible in Swagger UI.
\f
This part is internal documentation and will be hidden from OpenAPI.
\"\"\"
return [{"item_id": "Foo"}]
Dependency Finalization
When using dependencies with yield inside a router, ensure that you do not use a bare except block that catches all exceptions without re-raising them. If an exception occurs and is swallowed, the AsyncExitStack used by FastAPI may fail to finalize the dependency correctly, leading to errors like "Response not awaited".
Router Merging
Unlike mounting sub-applications, APIRouter routes are merged directly into the main application's routing table. This means they share the same global state and middleware context as the rest of the FastAPI application.