and prevents secret leakage across modules.
- Early API versioning (
/api/v1/) reduces breaking-change incidents by 80% during schema evolution.
Core Solution
The following structure enforces strict separation of concerns while preserving FastAPI's lightweight philosophy. Each directory serves a distinct architectural responsibility:
.
βββ app/
β βββ api/
β β βββ v1/
β β βββ endpoints/
β β βββ router.py
β βββ core/
β βββ crud/
β βββ db/
β βββ models/
β βββ services/
β βββ main.py
βββ alembic/
βββ docs/
βββ scripts/
βββ tests/
βββ .env.example
βββ alembic.ini
βββ docker-compose.yaml
βββ Dockerfile
βββ pyproject.toml
βββ README.md
app/main.py
This is the application entry point.
Use it to create the FastAPI() app, register routers, configure lifespan events, add middleware, and expose basic endpoints like /health.
Try not to put every route here. If main.py grows every time you add a feature, it is probably doing too much.
app/api/v1/
This folder contains your versioned API.
A common pattern is:
api/
βββ v1/
βββ endpoints/
β βββ login.py
β βββ users.py
βββ router.py
Each file in endpoints/ handles one feature or resource. For example, users.py contains user-related routes.
The router.py file combines those endpoint routers, so main.py only needs to include one versioned router:
app.include_router(api_router, prefix="/api/v1")
Versioning early makes future changes easier.
app/core/
Use core/ for app-wide configuration and security code.
This usually includes:
Environment-based settings
Secret keys
JWT helpers
Password hashing
Authentication utilities
These concerns are used across the app, so keeping them separate avoids duplication.
app/models/
The models/ folder stores your data shapes.
Depending on your stack, this may include SQLModel models, Pydantic schemas, database table models, request models, and response models.
A useful rule: do not assume your database model should also be your API response model. For example, a user table may contain a hashed password, but your API response should not.
app/crud/
Use crud/ for reusable database operations.
Instead of writing database queries directly inside route handlers, keep them in focused functions like:
Create user
Get user by ID
Get user by email
Update user
Delete user
This keeps endpoints cleaner and database behavior easier to test.
app/db/
The db/ folder handles database setup.
It often contains the database engine, session helpers, connection logic, and initial seed data.
This keeps infrastructure concerns separate from API logic.
app/services/
Use services/ for business logic and integrations.
If a route needs to coordinate multiple steps, call an external API, send an email, or apply business rules, that logic usually belongs in a service.
Endpoints should describe the HTTP interface. Services should describe what the application actually does.
alembic/
alembic/ stores database migrations.
Models describe the current shape of your data. Migrations describe how the database changes over time.
For real applications, migrations are essential.
tests/
Your tests should be easy to find and understand.
One simple approach is to mirror your API structure:
tests/
βββ api/
βββ v1/
βββ endpoints/
βββ test_login.py
βββ test_users.py
Start by testing behavior that users and clients depend on: login, user creation, validation, permissions, and health checks.
Supporting files
Files like Dockerfile, docker-compose.yaml, .env.example, pyproject.toml, scripts/, and docs/ are part of a healthy backend project too.
They help with local development, dependency management, deployment, documentation, and onboarding.
Generated folders like .venv/, pycache/, .pytest_cache/, and build outputs should usually stay out of your source structure and be ignored by Git.
Final thoughts
A good FastAPI structure should make the next feature easier to add.
Keep routes thin, move business logic into services, keep database logic reusable, separate config from feature code, and organize tests around behavior.
You do not need a complex architecture on day one. But once your API starts growing, a clean structure gives your project room to breathe.
Pitfall Guide
- Coupling Database Models with API Schemas: Never expose raw ORM/SQLModel objects directly in responses. Database models often contain sensitive fields (e.g., hashed passwords, internal flags) or lazy-loaded relationships that break serialization. Always map to dedicated Pydantic request/response schemas.
- Embedding Business Logic in Endpoints: Route handlers must remain thin HTTP adapters. Complex workflows, third-party API orchestration, or multi-step validations belong in
services/. Violating this creates untestable endpoints and tight coupling to the web framework.
- Ignoring Early API Versioning: Skipping
/api/v1/ prefixes leads to breaking changes when schemas evolve. Versioning isolates client contracts and allows parallel deprecation strategies without disrupting production traffic.
- Mixing Infrastructure with Application Logic: Database engine initialization, connection pooling, and session management should reside exclusively in
db/. Scattering these across endpoints or main.py causes resource leaks, connection exhaustion, and difficult environment switching.
- Neglecting Test Structure Mirroring: Tests should strictly mirror the
api/v1/endpoints/ layout. Random test placement obscures coverage metrics, complicates mocking strategies, and makes regression tracking nearly impossible in large teams.
- Committing Generated/Cache Artifacts:
.venv/, __pycache__/, .pytest_cache/, and build directories bloat repository history and cause cross-environment conflicts. Always enforce .gitignore rules to maintain a clean, reproducible codebase.
Deliverables
- Blueprint: A production-ready directory scaffold with pre-configured
pyproject.toml (Poetry/PDM compatible), alembic.ini migration hooks, docker-compose.yaml for local DB/Redis services, and Dockerfile multi-stage builds. Includes placeholder modules for core/settings.py, db/session.py, and api/v1/router.py.
- Checklist: