Keeping every endpoint in main.py does not scale. APIRouter lets you split path operations into modules and mount them on the app with prefixes and tags.
# routers/users.py
from fastapi import APIRouter
router = APIRouter(
prefix="/users",
tags=["users"],
dependencies=[], # router-wide dependencies (e.g. auth)
)
@router.get("/")
def list_users():
return [{"username": "alice"}]
@router.get("/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}
# main.py
from fastapi import FastAPI
from routers import users, items
app = FastAPI()
app.include_router(users.router)
app.include_router(items.router, prefix="/api", tags=["items"])
prefix can be set on the router or at include_router time (they concatenate).tags group endpoints in the docs UI./api/v1 and /api/v2).app/
├── main.py # app factory, middleware, include routers
├── core/
│ ├── config.py # pydantic-settings
│ └── security.py # hashing, JWT
├── db/
│ └── session.py # engine, SessionLocal
├── models/ # SQLAlchemy / DB models
├── schemas/ # Pydantic models (in/out)
├── crud/ # DB operations per entity
├── routers/ # APIRouter per entity
│ ├── users.py
│ └── items.py
├── dependencies.py # shared Depends (get_db, current_user)
└── tests/
Rule of thumb: schemas never import from models logic-wise they are independent; routers depend on crud and schemas; crud depends on models. This keeps layers swappable and testable.
app_v1 = FastAPI()
app_v1.include_router(users_v1.router, prefix="/v1")
app = FastAPI()
app.mount("/api", app_v1) # sub-application mounting
Alternatively use prefixes only (prefix="/api/v1") — simpler and usually enough until you need truly different apps.
A: Yes, and the first matching route registered wins. This is a common source of "my endpoint returns the wrong thing" bugs — check include order, especially for a catch-all like /{item_id} declared before a fixed path.
A: For anything beyond a demo, use a factory: def create_app() -> FastAPI: that wires routers, middleware and events. It makes testing (fresh app per suite) and different configs per environment trivial.
A: In a central dependencies.py (or per-domain module). Routers import them and declare db: Session = Depends(get_db) — never create sessions directly inside endpoints.
APIRouter is how FastAPI apps grow: one router per resource, mounted with prefixes and tags, backed by a layered folder structure (routers → crud → models, with pydantic schemas at the boundary). Start simple, but structure early.