Skip to main content
As your application grows, you’ll want to organize it across multiple files. FastAPI provides APIRouter to help you structure larger applications in a clean, modular way.

Application Structure

For bigger applications, you typically organize code like this:
app/
├── __init__.py
├── main.py
├── dependencies.py
└── routers/
    ├── __init__.py
    ├── users.py
    └── items.py

APIRouter

The APIRouter class works similarly to FastAPI - it allows you to define path operations that can later be included in your main application.

Creating a Router

Create a router in a separate file (e.g., routers/users.py):
from fastapi import APIRouter

router = APIRouter()

@router.get("/users/", tags=["users"])
async def read_users():
    return [{"username": "Rick"}, {"username": "Morty"}]

@router.get("/users/me", tags=["users"])
async def read_user_me():
    return {"username": "fakecurrentuser"}

@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
    return {"username": username}

Router with Prefix and Tags

You can configure routers with common parameters:
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"}},
)

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"}

Including Routers

In your main application file (main.py), include the routers:
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])

app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)

@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}
When using prefix, don’t include leading slashes in your router’s path operations. The prefix is automatically prepended.

Router Parameters

The APIRouter accepts several parameters:
1

prefix

A path prefix for all routes in the router. Must start with / and not end with /.
router = APIRouter(prefix="/items")
2

tags

Tags to apply to all path operations in the router for OpenAPI documentation.
router = APIRouter(tags=["items", "inventory"])
3

dependencies

Dependencies to apply to all path operations in the router.
router = APIRouter(dependencies=[Depends(verify_token), Depends(verify_key)])
4

responses

Additional responses to show in OpenAPI documentation.
router = APIRouter(responses={404: {"description": "Not found"}})

include_router() Parameters

When including a router in your app, you can override or add to the router’s configuration:
app.include_router(
    items.router,
    prefix="/api/v1",  # Additional prefix
    tags=["v1"],  # Additional tags
    dependencies=[Depends(api_key_header)],  # Additional dependencies
    responses={500: {"description": "Internal server error"}},  # Additional responses
)
Parameters specified in include_router() are added to those defined in the router itself. Both sets of tags, dependencies, and responses will be applied.

Multiple Routers

You can include the same router multiple times with different configurations:
app.include_router(items.router, prefix="/api/v1", tags=["v1"])
app.include_router(items.router, prefix="/api/v2", tags=["v2"])

Nested Routers

You can also include routers within other routers:
from fastapi import APIRouter

api_router = APIRouter()
api_router.include_router(users.router, prefix="/users")
api_router.include_router(items.router, prefix="/items")

app.include_router(api_router, prefix="/api/v1")
Be careful with dependencies and middleware when nesting routers deeply. Each level adds its dependencies to the execution chain.

Benefits

1

Modularity

Keep related endpoints together in separate files, making your codebase easier to navigate and maintain.
2

Reusability

Reuse routers in different applications or include the same router multiple times with different configurations.
3

Team Collaboration

Different team members can work on different routers without conflicts.
4

Testing

Test routers independently before integrating them into the main application.

Path Operation Uniqueness

Make sure that path operations across all routers are unique. Having duplicate paths with the same HTTP method will cause the last one to override previous ones.
# In users.router
@router.get("/status")
async def user_status():
    return {"status": "user ok"}

# In items.router - This will conflict if both have the same prefix!
@router.get("/status")
async def item_status():
    return {"status": "items ok"}
Use unique prefixes for each router to avoid path conflicts, or carefully plan your API structure.

Build docs developers (and LLMs) love