Difficulty: Beginner
As your FastAPI application grows beyond a handful of endpoints, you need a strategy for organizing routes into logical modules. FastAPI provides the APIRouter class for exactly this purpose. An APIRouter works just like the main FastAPI app - you define routes on it with the same decorators - but it can be mounted into the main app at a specific prefix.
Think of APIRouter as a mini-FastAPI application. You create an APIRouter instance, define routes on it, and then include it in the main app using app.include_router(). You can set a prefix (like /api/v1/users) and tags (for grouping in documentation) when creating the router or when including it.
A common pattern is to create one router file per resource. For example, a users.py file contains all user-related endpoints, an items.py file contains all item-related endpoints, and so on. Each file exports a router, and the main app imports and includes all routers. This keeps each file small and focused.
Router prefixes eliminate repetition in path definitions. Instead of writing @router.get("/users"), @router.get("/users/{id}"), @router.post("/users") with the /users prefix repeated everywhere, you set prefix="/users" on the router and then write @router.get("/"), @router.get("/{id}"), @router.post("/"). The prefix is automatically prepended to all routes.
Tags group endpoints in the Swagger UI documentation. When you set tags=["Users"] on a router, all its endpoints appear under a 'Users' section in the docs. This makes the documentation easier to navigate, especially for APIs with many endpoints.
You can also set shared dependencies, responses, and other parameters on the router that apply to all routes within it. For example, setting dependencies=[Depends(verify_token)] on a router means every route in that router requires authentication, without needing to repeat the dependency on each individual endpoint.
For larger applications, you might have nested routers or a router that includes other routers. FastAPI supports this - you can include a router within another router, creating a hierarchy. This is useful for versioned APIs where you have /api/v1/ and /api/v2/ top-level prefixes, each including the same set of resource routers.
# routers/users.py
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(
prefix="/users",
tags=["Users"],
)
class UserCreate(BaseModel):
name: str
email: str
@router.get("/")
def list_users():
return [{"id": 1, "name": "Alice"}]
@router.get("/{user_id}")
def get_user(user_id: int):
return {"id": user_id, "name": "Alice"}
@router.post("/", status_code=201)
def create_user(user: UserCreate):
return {"id": 1, user.model_dump()}
The APIRouter is defined with a prefix and tags. Routes use relative paths (/ instead of /users) because the prefix handles the base path. All endpoints will appear under the 'Users' tag in Swagger UI.
# main.py
from fastapi import FastAPI
from routers import users, items, auth
app = FastAPI(title="My API", version="1.0.0")
# Include routers
app.include_router(users.router)
app.include_router(items.router)
app.include_router(auth.router)
# Or override prefix/tags when including
app.include_router(
users.router,
prefix="/api/v1/users", # Overrides router's prefix
tags=["Users V1"], # Overrides router's tags
)
@app.get("/")
def root():
return {"message": "Welcome to the API"}
# Resulting routes:
# GET / -> root()
# GET /users -> list_users()
# GET /users/{id} -> get_user()
# POST /users -> create_user()
# (plus all routes from items and auth routers)
The main app imports and includes each router. You can override the prefix and tags at include time if needed. The root endpoint is defined directly on the app since it does not belong to any specific resource.
# Project structure:
# app/
# main.py
# routers/
# __init__.py
# users.py
# items.py
# app/routers/__init__.py
from .users import router as users_router
from .items import router as items_router
__all__ = ["users_router", "items_router"]
# app/main.py
from fastapi import FastAPI
from .routers import users_router, items_router
app = FastAPI()
app.include_router(users_router)
app.include_router(items_router)
# This pattern scales well:
# - Each resource has its own file
# - The __init__.py provides clean imports
# - main.py is a thin assembly file
This structure keeps each resource module independent. The routers __init__.py provides clean re-exports so main.py only needs simple imports. This scales well from small to large projects.
APIRouter, Prefixes, Tags, Include Router, Modular Design