FastAPI Intermediate: Structure, Dependencies and Data
Routers and Project Structure
In this lesson you will
- Organise an API into routers and modules
- Use a lifespan function for start-up tasks
- Keep models, database code and routes separate
Introduction
One file is fine for a first app, but a real API soon has many endpoints, models and settings. Splitting them into modules keeps each part easy to find and change. FastAPI's tool for this is the APIRouter. The project below was created and its tests were run with Python 3.13 and FastAPI 0.162.
A sensible layout
library/
app/
__init__.py
main.py creates the app and includes routers
db.py engine, sessions and dependencies
models.py data models
routers/
__init__.py
books.py all /books endpoints
test_books.pyRules of thumb: routes handle HTTP, models describe data, db handles storage. As the app grows you can add services.py for business logic and settings.py for configuration.
An APIRouter
A router groups related endpoints and can share a prefix and tags.
Python
# app/routers/books.py
from fastapi import APIRouter, HTTPException
router = APIRouter(prefix="/books", tags=["books"])
@router.get("/{book_id}")
def get_book(book_id: int):
raise HTTPException(status_code=404, detail="Book not found")Because of prefix="/books", this route is served at /books/{book_id}. tags groups the endpoints in /docs.
The main file
Python
# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.db import create_db_and_tables
from app.routers import books
@asynccontextmanager
async def lifespan(app: FastAPI):
create_db_and_tables() # runs once at start-up
yield # the app serves requests here
# code after yield runs at shutdown
app = FastAPI(title="Library API", lifespan=lifespan)
app.include_router(books.router)
@app.get("/health")
def health():
return {"status": "ok"}include_routeradds all the router's endpoints to the app.- The lifespan function is the modern place for start-up and shutdown work, such as creating tables or opening connections. Code before
yieldruns at start, and code after it at shutdown. - A
/healthendpoint lets monitoring tools check that the app is up.
Run from the project folder with uvicorn app.main:app --reload.
Quick quiz
What does prefix='/books' on an APIRouter do?
Absolute imports and packages
The __init__.py files make folders into Python packages, so from app.db import ... works when you run from the project root. If you see import errors, check you start the server from the folder that contains app, and that package folders have __init__.py.
Routers can nest
A router can include other routers, and each can set dependencies for all of its routes, such as requiring a logged-in user for everything in /admin. The next lesson shows dependencies.
Keep it simple
Do not create layers before you need them. Start with the layout above, and split further when a file becomes hard to navigate.
Try it yourself
Create the folder layout. Move two endpoints into routers/books.py, include the router in main.py and check /docs shows them under the books tag.
Summary
- APIRouter groups related endpoints with a shared prefix and tags, and include_router attaches them to the app.
- A simple layout separates routes, models and database code.
- Use a lifespan function for start-up and shutdown work, and add a health endpoint.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading