Skip to main content

Modularizing Applications with include_router

When your main.py grows to hundreds of lines, finding a specific endpoint or managing dependencies becomes difficult. FastAPI provides the APIRouter class to split your application into logical modules across multiple files, allowing you to group related path operations and apply shared configurations like prefixes, tags, and dependencies.

Creating and Including Routers

To modularize your application, you define an APIRouter instance in a separate file and use it to register path operations just as you would with a FastAPI instance.

In a file like routers/items.py, you create the router:

from fastapi import APIRouter

router = APIRouter()

@router.get("/items/")
async def read_items():
return [{"name": "Empanada"}, {"name": "Arepa"}]

Then, in your main application file (main.py), you include this router using the include_router method:

from fastapi import FastAPI
from .routers import items

app = FastAPI()

app.include_router(items.router)

Internally, FastAPI itself uses an APIRouter instance (stored in self.router) to manage routes. When you call app.include_router(router), FastAPI iterates through all the routes defined in your submodule's router and re-registers them on the main application's router.

Applying Path Prefixes

You can define a prefix for all path operations in a router. This is useful for versioning or grouping resources without repeating the path in every decorator.

router = APIRouter(prefix="/items")

# This will be served at /items/
@router.get("/")
async def read_items():
return [{"item_id": "portal-gun"}]

FastAPI enforces strict formatting for prefixes in fastapi/routing.py:

  • A prefix must start with a forward slash /.
  • A prefix must not end with a forward slash /.

If you violate these rules, FastAPI raises an AssertionError during initialization:

# This will raise: "A path prefix must start with '/'"
router = APIRouter(prefix="items")

# This will raise: "A path prefix must not end with '/', as the routes will start with '/'"
router = APIRouter(prefix="/items/")

Inheriting Tags and Dependencies

APIRouter allows you to define configuration that applies to every route it contains. When a router is included, these configurations are merged with any existing ones.

Tags

Tags are used to group operations in the generated OpenAPI schema (Swagger UI). If you provide tags at the router level, they are appended to the tags of each individual route.

router = APIRouter(tags=["items"])

@router.get("/items/", tags=["search"])
async def search_items():
...

In the example above, the search_items operation will have both the "items" and "search" tags in the documentation.

Dependencies

Dependencies defined on an APIRouter are executed for every path operation in that router.

from fastapi import APIRouter, Depends
from .dependencies import get_token_header

router = APIRouter(
dependencies=[Depends(get_token_header)]
)

When you include a router that has its own dependencies into another router (or the main app) that also has dependencies, FastAPI extends the list. The add_api_route method in fastapi/routing.py handles this by copying the router's dependencies and extending them with the route-specific ones:

# Internal logic in APIRouter.add_api_route
current_dependencies = self.dependencies.copy()
if dependencies:
current_dependencies.extend(dependencies)

Nesting Routers

FastAPI supports nesting routers within other routers. This allows for deeply structured applications where sub-modules can have their own prefixes and dependencies.

from fastapi import APIRouter

parent_router = APIRouter(prefix="/api/v1")
child_router = APIRouter(prefix="/users")

@child_router.get("/")
async def get_users():
return [{"username": "johndoe"}]

parent_router.include_router(child_router)
# The route is now at /api/v1/users/

Validation and Constraints

  • Self-Inclusion: You cannot include a router into itself. APIRouter.include_router checks this with an assertion: assert self is not router.
  • Empty Paths: If you do not provide a prefix when including a router, and one of the routes in that router also has an empty path, FastAPI raises a FastAPIError. This prevents ambiguous routes that have no path at all.

Merging Responses

When including a router, you can provide additional responses that apply to all its routes. This is useful for documenting common error codes like 404 Not Found or 401 Unauthorized for a specific group of endpoints.

router = APIRouter(
responses={404: {"description": "Item not found"}}
)

In include_router, FastAPI merges these dictionaries. If a route already has a response for a specific status code, the route's specific response takes precedence because the router's responses are used as the base:

# Internal logic in APIRouter.include_router
combined_responses = {**responses, **route.responses}

This ensures that the most specific documentation (the one defined on the function decorator) is what ends up in the OpenAPI schema.