General
Mastering Routing in FastAPI: From Flat Files to Clean Architecture
0 views

When building an API with FastAPI, it’s deceptively easy to dump every endpoint into main.py. A few @app.get() decorators here, a couple of @app.post() decorators there, and everything works.
Then your project grows. Suddenly, main.py is an 800-line monolith, team members are constantly hitting Git merge conflicts, and nobody knows where the user authentication logic ends and the billing logic begins.
FastAPI solves this with APIRouter. Here is how to structure your routing cleanly from day one.
1. The Core Tool: APIRouter
Think of APIRouter as a mini FastAPI app that doesn’t run on its own. It groups related path operations together, which you then mount onto your main application.
A typical modular directory structure looks like this:
my_project/
├── app/
│ ├── routers/
│ │ ├── users.py
│ │ └── items.py
│ ├── dependencies.py
│ └── main.py
└── requirements.txt
In your module (app/routers/users.py), define your router:
from fastapi import APIRouter, HTTPException
router = APIRouter()
@router.get("/")
async def list_users():
return [{"id": 1, "username": "alice"}]
@router.get("/{user_id}")
async def get_user(user_id: int):
return {"id": user_id, "username": "alice"}
Then, register it in app/main.py:
from fastapi import FastAPI
from app.routers import users
app = FastAPI()
app.include_router(users.router, prefix="/users", tags=["Users"])
Two key parameters to notice:
prefix: Prepend /users to every endpoint in the router. Notice how users.py uses / and /{user_id} instead of repeating /users everywhere.
tags: Groups these endpoints together in FastAPI's auto-generated Swagger UI (/docs).
2. Cleaner Endpoints with Shared Dependencies
One of the biggest advantages of APIRouter is attaching security checks or common dependencies to an entire group of routes at once.
Instead of writing Depends(verify_token) on 15 separate endpoints, apply it to the router level:
# app/routers/admin.py
from fastapi import APIRouter, Depends
from app.dependencies import require_admin_role
# Every route attached to this router will now require admin access
router = APIRouter(
prefix="/admin",
tags=["Admin"],
dependencies=[Depends(require_admin_role)]
)
@router.delete("/purge-cache")
async def purge_cache():
return {"status": "cache cleared"}
3. Nesting Routers for Sub-Resources
Real-world APIs often have hierarchical relationships—such as comments belonging to a specific post (/posts/{post_id}/comments).
Rather than hardcoding long prefix strings, routers can be nested inside other routers:
# app/routers/comments.py
from fastapi import APIRouter
comments_router = APIRouter()
@comments_router.get("/")
async def get_comments(post_id: int):
return {"post_id": post_id, "comments": []}
# app/routers/posts.py
from fastapi import APIRouter
from app.routers.comments import comments_router
posts_router = APIRouter(prefix="/posts", tags=["Posts"])
# Mount comments directly under a specific post
posts_router.include_router(
comments_router,
prefix="/{post_id}/comments",
tags=["Comments"]
)
Now, mounting posts_router inside your main.py automatically exposes both /posts and /posts/{post_id}/comments cleanly.
4. API Versioning Without Headaches
Versioning is simple when your routing is modular. You can bundle whole sets of routers under a version prefix:
# app/main.py
from fastapi import FastAPI, APIRouter
from app.routers import users, items
app = FastAPI()
# Create a v1 router
api_v1 = APIRouter(prefix="/api/v1")
api_v1.include_router(users.router, prefix="/users", tags=["Users"])
api_v1.include_router(items.router, prefix="/items", tags=["Items"])
# Include the full v1 bundle into the main app
app.include_router(api_v1)
When it's time for v2, you can spin up an api_v2 router alongside it without touching older client integrations.
Read original: https://dev.to/itsdevcode/mastering-routing-in-fastapi-from-flat-files-to-clean-architecture-427g
PreviousMastering React Server Components: Understanding the Server/Client BoundaryNextMatching Blood Donors by GPS: The Geospatial Query Design Behind GeoBlood
Related posts
Moving our warehouse off DuckDB
0Hacker News
Show HN: VHS Camera – Run your camera or videos through a simulated VHS signal
0Hacker News
Bitmovin (YC S15) Is Hiring Technical Graduates in Denver (Engineering/CS Focus)
0Hacker News
Device mockups and animated launch videos in your browser, no account needed.
0dev.to Design
Vellinko – Sign PDFs in the browser without uploading them
0Hacker News New
Show HN: Vivio House, an interactive 3D headquarters
0Hacker News New