Structuring Applications with APIRouter
In large applications, keeping all path operations in a single file becomes difficult to maintain. The APIRouter class allows you to group related routes into separate modules, which can then be integrated into your main application.
By the end of this tutorial, you will have a structured application with routes organized into a dedicated module, complete with shared prefixes, tags, and dependencies.
Prerequisites
To follow this tutorial, you should have a basic FastAPI application structure. This codebase uses Pydantic for data validation and Starlette for routing.
Step 1: Create a Router Module
First, create a new file (e.g., routers/items.py) to house a specific set of related endpoints. Instead of using the FastAPI class, you will use APIRouter.
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
In this step, you initialize the APIRouter. The parameters you pass here apply to every route defined on this router:
- prefix: Prepends
/itemsto all paths. Note that the prefix must start with/and must not end with/, as enforced by theAPIRouterconstructor. - tags: Adds the "items" tag to all operations for OpenAPI documentation.
- dependencies: Executes the
get_token_headerdependency for every request to these routes. - responses: Defines a default 404 response for all routes in this group.
Step 2: Define Path Operations
Now, add path operations to your router using decorators like @router.get() and @router.put().
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Even though the @router.get("/") path looks like the root, it will actually be served at /items/ because of the prefix defined in Step 1. For the update_item operation, the tags and responses you provide are merged with the router's defaults.
Step 3: Include the Router in the Main App
To make these routes active, you must include the router in your main FastAPI application file (e.g., main.py).
from fastapi import Depends, FastAPI
from .routers import items, users
from .dependencies import get_query_token
app = FastAPI(dependencies=[Depends(get_query_token)])
# Include the router defined in Step 1 & 2
app.include_router(items.router)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
The app.include_router() method registers all the routes from items.router into the main application.
Step 4: Override Configuration During Inclusion
You can also apply or override configurations at the moment you include a router. This is useful for reusing the same router in different contexts.
from .internal import admin
from .dependencies import get_token_header
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
When you provide parameters to include_router, FastAPI handles them as follows:
- Prefixes: The new prefix is prepended to any prefix already defined on the router.
- Tags and Dependencies: These are appended to the existing lists.
- Responses: These are merged with existing responses.
Step 5: Nesting Routers
APIRouter also supports nesting. You can include one router inside another before finally including the parent router in the FastAPI app.
# Inside a hypothetical internal_router.py
from fastapi import APIRouter
from .routers import users_router
internal_router = APIRouter(prefix="/internal")
internal_router.include_router(users_router, prefix="/users")
# Resulting paths will be /internal/users/...
Summary
You have successfully organized your application by:
- Creating an
APIRouterinstance in a separate module. - Configuring shared prefixes and dependencies at the router level.
- Integrating the router into the main
FastAPIapplication usinginclude_router.
This structure keeps your main.py clean and allows teams to work on different modules independently. For next steps, explore adding custom lifespan handlers to your routers to manage startup and shutdown logic for specific modules.