Skip to main content

FastAPI Mounted Routers

In large applications, organizing routes becomes crucial for maintainability. FastAPI's mounted routers allow you to group related endpoints together, making your code more modular and easier to manage.

Introduction

As your FastAPI application grows, having all routes in a single file becomes unwieldy. Mounted routers provide a solution by allowing you to:

  • Split your API into logical modules
  • Group related endpoints together
  • Maintain clean separation of concerns
  • Apply common configurations to groups of endpoints

Let's explore how to use mounted routers to structure your FastAPI applications effectively.

Basic Router Creation and Mounting

To create a router in FastAPI, you use the APIRouter class.

python
from fastapi import APIRouter, FastAPI

app = FastAPI()

# Create a router
router = APIRouter()

# Define routes on the router
@router.get("/items/")
async def read_items():
return [{"name": "Item 1"}, {"name": "Item 2"}]

# Mount the router to the main app
app.include_router(router)

In this example, the endpoint /items/ can be accessed just as if it were defined directly on the app.

Router with Custom Prefix

One of the major benefits of routers is the ability to add a prefix to all routes in a group:

python
from fastapi import APIRouter, FastAPI

app = FastAPI()

# Create a router with a prefix
router = APIRouter(
prefix="/api/v1",
)

@router.get("/users/") # Becomes /api/v1/users/
async def read_users():
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

@router.get("/items/") # Becomes /api/v1/items/
async def read_items():
return [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]

app.include_router(router)

Now all routes defined on this router will be prefixed with /api/v1, so the full paths become /api/v1/users/ and /api/v1/items/.

Organizing Routes by Module

A common pattern is to organize routes by domain or feature. Let's see how to structure a more complex application:

python
# File structure:
# - main.py
# - routers/
# - users.py
# - items.py
# - orders.py

users.py

python
from fastapi import APIRouter

router = APIRouter(
prefix="/users",
tags=["users"],
responses={404: {"description": "Not found"}},
)

@router.get("/")
async def read_users():
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

@router.get("/{user_id}")
async def read_user(user_id: int):
return {"id": user_id, "name": f"User {user_id}"}

items.py

python
from fastapi import APIRouter

router = APIRouter(
prefix="/items",
tags=["items"],
responses={404: {"description": "Not found"}},
)

@router.get("/")
async def read_items():
return [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]

@router.get("/{item_id}")
async def read_item(item_id: int):
return {"id": item_id, "name": f"Item {item_id}"}

main.py

python
from fastapi import FastAPI
from routers import users, items

app = FastAPI()

# Mount the routers
app.include_router(users.router)
app.include_router(items.router)

@app.get("/")
async def root():
return {"message": "Welcome to our API!"}

In this structure, we've organized our routes into separate modules and mounted them on the main app. Each router has its own prefix and tag, making the API documentation clearer and more organized.

Adding Tags for Documentation

Tags help organize your API documentation in the Swagger UI. By adding tags to routers, all endpoints in that router will inherit those tags:

python
router = APIRouter(
prefix="/users",
tags=["users"],
)

When you open the interactive documentation (at /docs), you'll see endpoints grouped by these tags.

Adding Default Response Descriptions

You can also add default responses that will be applied to all endpoints in a router:

python
router = APIRouter(
prefix="/users",
tags=["users"],
responses={404: {"description": "User not found"}},
)

This helps maintain consistency in your API documentation without repeating the same information for each endpoint.

Advanced Router Configuration

Let's look at more advanced configurations for routers:

Router Dependencies

You can add dependencies that apply to all routes in a router:

python
from fastapi import APIRouter, Depends, Header, HTTPException

async def verify_token(x_token: str = Header()):
if x_token != "valid-token":
raise HTTPException(status_code=400, detail="Invalid X-Token header")

router = APIRouter(
prefix="/protected",
tags=["protected"],
dependencies=[Depends(verify_token)],
responses={401: {"description": "Unauthorized"}},
)

@router.get("/data/")
async def read_protected_data():
# This endpoint will only be reached if verify_token passes
return {"data": "protected data"}

All endpoints in this router will require a valid token in the X-Token header.

Router Callbacks

You can also add callbacks to be executed when including a router:

python
def router_startup_event():
print("Router startup")

def router_shutdown_event():
print("Router shutdown")

router = APIRouter(
prefix="/items",
tags=["items"],
on_startup=[router_startup_event],
on_shutdown=[router_shutdown_event],
)

Nested Routers

You can even mount routers onto other routers for deeply nested APIs:

python
from fastapi import APIRouter

main_router = APIRouter(prefix="/api/v1")

users_router = APIRouter(prefix="/users", tags=["users"])

@users_router.get("/")
async def read_users():
return [{"id": 1, "name": "Alice"}]

# Mount users_router onto main_router
main_router.include_router(users_router)

# The final path will be /api/v1/users/

Real-World Example: Building a Blog API

Let's build a more complete example of a blog API with different resources:

python
# main.py
from fastapi import FastAPI
from routers import posts, comments, users, auth

app = FastAPI(
title="Blog API",
description="A simple blog API with FastAPI",
version="1.0.0"
)

app.include_router(auth.router)
app.include_router(users.router)
app.include_router(posts.router)
app.include_router(comments.router)

@app.get("/")
async def root():
return {"message": "Welcome to the Blog API"}
python
# routers/auth.py
from fastapi import APIRouter, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

router = APIRouter(
prefix="/auth",
tags=["authentication"],
)

@router.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# In a real app, verify credentials against a database
if form_data.username == "demo" and form_data.password == "password":
return {"access_token": "example-token", "token_type": "bearer"}
raise HTTPException(status_code=401, detail="Invalid credentials")
python
# routers/users.py
from fastapi import APIRouter, Depends

router = APIRouter(
prefix="/users",
tags=["users"],
)

@router.get("/")
async def get_users():
return [
{"id": 1, "username": "johndoe", "email": "[email protected]"},
{"id": 2, "username": "janedoe", "email": "[email protected]"}
]

@router.get("/me")
async def get_current_user():
return {"id": 1, "username": "johndoe", "email": "[email protected]"}
python
# routers/posts.py
from fastapi import APIRouter

router = APIRouter(
prefix="/posts",
tags=["posts"],
)

@router.get("/")
async def get_posts():
return [
{"id": 1, "title": "First Post", "content": "Hello world", "author_id": 1},
{"id": 2, "title": "Second Post", "content": "FastAPI is awesome", "author_id": 2}
]

@router.get("/{post_id}")
async def get_post(post_id: int):
return {"id": post_id, "title": f"Post {post_id}", "content": "Content", "author_id": 1}
python
# routers/comments.py
from fastapi import APIRouter

router = APIRouter(
prefix="/posts/{post_id}/comments",
tags=["comments"],
)

@router.get("/")
async def get_comments(post_id: int):
return [
{"id": 1, "text": "Great post!", "author_id": 2, "post_id": post_id},
{"id": 2, "text": "Thanks for sharing", "author_id": 1, "post_id": post_id}
]

@router.post("/")
async def create_comment(post_id: int, text: str):
return {"id": 3, "text": text, "author_id": 1, "post_id": post_id}

This example demonstrates how mounted routers help organize a more complex API with multiple resources and relationships between them.

Best Practices for Mounted Routers

When working with mounted routers, follow these best practices:

  1. Logical Organization: Group endpoints based on resources or features
  2. Consistent Naming: Use consistent naming conventions for route paths
  3. Apply Tags: Use tags to organize your API documentation
  4. Use Prefixes: Apply prefixes to avoid route conflicts
  5. Common Dependencies: Apply shared dependencies at the router level
  6. Separate Business Logic: Keep business logic separate from route handlers

Summary

FastAPI mounted routers are a powerful tool for organizing your API into modular, maintainable components. By grouping related endpoints together and applying common configurations at the router level, you can build scalable applications that remain clean and easy to understand as they grow.

Key benefits of mounted routers include:

  • Logical organization of endpoints
  • Code reusability
  • Easier maintenance
  • Cleaner documentation
  • Better separation of concerns

Additional Resources

Exercises

  1. Create a FastAPI application with routers for a simple e-commerce system (users, products, orders)
  2. Add appropriate tags and prefixes to each router
  3. Implement a common authentication dependency for protected routes
  4. Create nested routers to handle hierarchical resources
  5. Add OpenAPI documentation responses at the router level


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)