++++A walkthrough of the FastAPI template — what it solves, how the pieces fit together, and why every decision was made the way it was.
1. Introduction
What This Template Solves
Most FastAPI tutorials end at "create a route, hit the database, return a dict." That works for demos, but it falls apart fast in a real codebase — routes accumulate business logic, ORM objects start leaking into responses, and testing anything becomes a pain because nothing is separated.
This template is the corrected starting point. It applies Clean Architecture principles to a FastAPI + SQLAlchemy stack, enforcing a strict unidirectional flow:
router → service → repository → persistenceEach layer has one job. Routers handle HTTP. Services own business logic. Repositories translate between the domain and the database. Nothing bleeds across those lines.
How the App Boots
When the app starts, create_app() in server/api/src/main.py wires everything together in a single place. The database engine gets created, the session factory is configured, and both services are instantiated:
# server/api/src/main.py
engine = create_async_engine("sqlite+aiosqlite:///./test.db")
async_session = async_sessionmaker(engine, expire_on_commit=False)
session = async_session()
repo = TodoRepository(session)
todo_service = TodoService(repo)
auth_service = AuthService()
app.state.todo_service = todo_service
app.state.auth_service = auth_serviceAfter that, middleware is attached (attach_request_logging, attach_auth_middleware), and routes are registered. The lifespan context handles startup and shutdown logging cleanly — no deprecated @app.on_event decorators anywhere.
The Anti-Pattern This Replaces
Here is what you get without any of this structure. Everything in one route: database access, business logic, and response shaping — all tangled together.
# What not to do
@router.post("/")
async def bad_handler(body: dict, request: Request):
repo = request.app.state.todo_service.repository
await repo.save(body) # persistence from the router
return {"ok": True, "raw": body} # leaking raw internalsThis pattern is everywhere in early FastAPI codebases. It works until you need to test it, reuse it, or change the database layer without touching the router.
The Pattern This Enforces
The corrected version delegates everything appropriately. The router receives a typed request, calls a service method, maps the domain model to a response schema, and returns it. That's all it does.
# server/api/src/routers/todo_router.py
@router.post("/", response_model=TodoResponse, status_code=status.HTTP_201_CREATED)
async def create_todo(body: CreateTodoRequest, service: TodoServiceDep):
todo = await service.create_todo(body.title, body.description)
return TodoResponse.from_domain(todo)The TodoServiceDep type alias in server/api/src/dependencies.py is how the router gets its service — via FastAPI's dependency injection, not by reaching into app.state directly. The TodoResponse.from_domain() call in server/shared/src/schemas/todo.py maps the domain model to a stable API contract, so internal fields never accidentally leak to the client.
What the Rest of This Series Covers
Every post in this series walks through one layer or cross-cutting concern in depth:
- Project Setup — dependencies, environment variables, and the bootstrap command
- Layered Architecture — what each layer owns and why violations are expensive
- Pydantic Boundary — request validation, normalization, and response mapping
- Repository Pattern — keeping ORM entities out of the service layer
- Migrations — versioned schema changes with upgrade and downgrade paths
- Auth Middleware — JWT bearer and basic auth with centralized enforcement
- Logging & Observability — request correlation and structured log events
- Testing & Quality — coverage strategy and CI gates
- Production Security Checklist — the controls that actually matter before shipping
Each post starts from the real source code in this template, so nothing is hypothetical. If you follow along with the repo open, you'll be able to trace every pattern back to the file that implements it.