FastAPI Routers and Organizing Code
As your API grows, putting every route in one main.py file becomes unmanageable. FastAPI's APIRouter lets you split routes into separate files grouped by feature. You then connect all routers into one central app.
The Problem with One Big File
main.py (500+ lines)
@app.get("/users")
@app.post("/users")
@app.get("/users/{id}")
@app.delete("/users/{id}")
@app.get("/products")
@app.post("/products")
@app.get("/orders")
@app.post("/orders")
... (hard to navigate)
The Goal: Feature-Based File Structure
project/ ├── main.py ← entry point, connects everything ├── routers/ │ ├── users.py ← all /users routes │ ├── products.py ← all /products routes │ └── orders.py ← all /orders routes └── models.py ← Pydantic models
Creating a Router in users.py
# routers/users.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/")
def list_users():
return [{"id": 1, "name": "Anjali"}]
@router.get("/{user_id}")
def get_user(user_id: int):
return {"id": user_id, "name": "Anjali"}
@router.post("/")
def create_user():
return {"message": "User created"}
Notice you use router instead of app. The paths here are relative — you set the prefix when you register the router.
Registering Routers in main.py
# main.py from fastapi import FastAPI from routers import users, products, orders app = FastAPI() app.include_router(users.router, prefix="/users", tags=["Users"]) app.include_router(products.router, prefix="/products", tags=["Products"]) app.include_router(orders.router, prefix="/orders", tags=["Orders"])
Result — active routes:
GET /users/
GET /users/{user_id}
POST /users/
GET /products/
GET /orders/
... and so on
How prefix + router path combine
app.include_router(users.router, prefix="/users")
Router path "/" → full path "/users/"
Router path "/{id}" → full path "/users/{id}"
Router path "/me" → full path "/users/me"
Adding Router-Level Dependencies
Apply a dependency to every route in a router without adding it to each function individually:
from fastapi import Depends
from auth import verify_token
app.include_router(
orders.router,
prefix="/orders",
tags=["Orders"],
dependencies=[Depends(verify_token)] ← applies to all order routes
)
Router-Level Responses
Set a default response model or status code for all routes in a router:
router = APIRouter(
prefix="/products",
tags=["Products"],
responses={404: {"description": "Product not found"}}
)
This response description appears in the docs for every route in this router.
Nested Routers
Routers can include other routers for deeper nesting:
# routers/admin/users.py admin_users_router = APIRouter() # routers/admin/__init__.py from fastapi import APIRouter from . import users admin_router = APIRouter(prefix="/admin") admin_router.include_router(users.admin_users_router, prefix="/users") # main.py app.include_router(admin_router) # Result: /admin/users/...
A Clean Project Structure for Medium Apps
project/ ├── main.py ├── routers/ │ ├── __init__.py │ ├── users.py │ ├── products.py │ └── orders.py ├── models/ │ ├── user.py │ └── product.py ├── dependencies.py ← shared Depends functions └── database.py ← DB connection setup
Key Points
- Use
APIRouterto group routes by feature into separate files. - Register routers in
main.pyusingapp.include_router(). - The
prefixargument prepends a path to every route in the router. - The
tagsargument groups all routes from that router in the docs. - Apply router-level dependencies to protect all routes in that router at once.
