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 APIRouter to group routes by feature into separate files.
  • Register routers in main.py using app.include_router().
  • The prefix argument prepends a path to every route in the router.
  • The tags argument groups all routes from that router in the docs.
  • Apply router-level dependencies to protect all routes in that router at once.

Leave a Comment

Your email address will not be published. Required fields are marked *