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.
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:
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:
# File structure:
# - main.py
# - routers/
# - users.py
# - items.py
# - orders.py
users.py
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
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
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:
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:
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:
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:
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:
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:
# 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"}
# 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")
# 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]"}
# 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}
# 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:
- Logical Organization: Group endpoints based on resources or features
- Consistent Naming: Use consistent naming conventions for route paths
- Apply Tags: Use tags to organize your API documentation
- Use Prefixes: Apply prefixes to avoid route conflicts
- Common Dependencies: Apply shared dependencies at the router level
- 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
- Create a FastAPI application with routers for a simple e-commerce system (users, products, orders)
- Add appropriate tags and prefixes to each router
- Implement a common authentication dependency for protected routes
- Create nested routers to handle hierarchical resources
- 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! :)