Technical
How to Structure a FastAPI Project That Scales
Every FastAPI tutorial shows you how to build a single-file API. None of them show you what happens when that file hits 500 lines and you have three developers working on it. Here is the project structure I use for every production FastAPI project.
The Problem with Single-File APIs
The FastAPI quickstart puts everything in main.py. That works for tutorials. In production, it means route handlers, database logic, validation rules, and utility functions all live in one file. Finding anything requires scrolling. Merge conflicts happen constantly.
The Structure That Works
src/
main.py # App creation, CORS, router mounts
models.py # Pydantic models (request/response schemas)
db.py # Database connection and client
routers/
posts.py # Blog post CRUD endpoints
categories.py # Category management endpoints
subscribers.py # Newsletter subscriber endpoints
newsletters.py # Newsletter send/schedule endpoints
utils/
slug.py # Slug generation from titles
reading_time.py # Word count to minutes calculationEach router file owns one resource. Each utility file does one thing. main.py stays clean because it only mounts routers and configures middleware.
The Router Pattern
FastAPI's APIRouter is the key. Each resource gets its own router:
# src/routers/posts.py
from fastapi import APIRouter
router = APIRouter(prefix='/posts', tags=['Posts'])
@router.post('', status_code=201)
async def create_post(post: PostCreate) -> PostResponse:
# handler logic here
pass
# src/main.py
from src.routers import posts, categories
app.include_router(posts.router)
app.include_router(categories.router)Why This Scales
When you add a new feature (say, newsletter scheduling), you create one new file: src/routers/newsletters.py. You add one line to main.py to mount it. Nothing else changes. No merge conflicts with the posts code. No risk of breaking existing endpoints.
The models file might eventually split too. When models.py gets large, create a models/ directory with posts.py, subscribers.py, and an __init__.py that re-exports everything.
Pydantic Models as the Contract
Keep all your Pydantic models in one place (or one directory). These models ARE your API contract. When the frontend developer needs to know what fields to send, they look at the models file, not the route handlers.
This separation also helps with testing. You can test models independently from routes, verifying validation rules without spinning up the server.
The Rule of One
Each file should have one responsibility. If you find yourself writing 'and' to describe what a file does ('it handles posts AND generates slugs AND calculates reading time'), split it. Small focused files are easier to find, easier to test, and easier to hand off.
See the FastAPI documentation on bigger applications for the official guidance on project structure.
RELATED READING
The Consulting Shift I Am Making In Year Two
After a year of writing and building, my consulting practice is changing shape. Shorter engagements. Sharper outcomes.
ReadThe Frontend Shift: Shipping Less JavaScript In Year Two
A year ago I reached for Next.js for everything. This year I often reach for nothing.
ReadThe Serverless Lesson I Would Write On A Sticky Note
After a year of shipping serverless projects, one rule explains most of the wins and all of the losses.
Read