A clean, layered architecture template for Python backend projects built with FastAPI — now packaged for 2026 with uv, Docker, Postgres, and a strict CI gate.
Based on the blueprint described in The Architecture Blueprint Every Python Backend Project Needs. A follow-up post on the upgrades in v0.2 is in progress.
app/
├── api/
│ └── v1/
│ ├── endpoints/ # Route handlers (thin wrappers, no business logic)
│ │ ├── health.py # /health and /ready
│ │ └── user.py
│ └── dependencies.py # Dependency injection (Annotated[..., Depends(...)])
├── core/
│ ├── config.py # Pydantic BaseSettings + SecretStr + placeholder rejection
│ ├── database.py # Async engine, session, commit-on-success boundary
│ ├── logging.py # structlog: JSON in prod, console in dev
│ ├── middleware.py # Request-ID middleware
│ └── security.py # JWT via PyJWT
├── models/
│ └── user.py # SQLAlchemy ORM models (database schema)
├── schemas/
│ └── user.py # Pydantic models (API request/response contracts)
├── services/
│ └── user_service.py # Business logic (framework-independent, testable)
├── repositories/
│ └── user_repo.py # Data access; commit lives at the session boundary
├── utils/
│ └── hashing.py # Shared helpers
└── main.py # Wiring only: routers, middleware, lifespan
tests/
├── unit/ # Isolated tests for services and utilities
└── integration/ # End-to-end API and database tests
migrations/ # Alembic (async) migrations
docker/ # Container entrypoint
Dockerfile
docker-compose.yml # Postgres 17 + app
Makefile # Common dev targets
pyproject.toml # PEP 621 metadata + tool config
uv.lock # Reproducible deps
.pre-commit-config.yaml # ruff + uv-lock + hygiene hooks
.github/workflows/ci.yml # Lint + mypy + pytest matrix (sqlite + postgres)
.env.example
| Layer | Directory | Responsibility |
|---|---|---|
| API | app/api/ |
Accept requests, validate input via schemas, delegate to services, return responses. No business logic. |
| Core | app/core/ |
Centralized config, security, database, logging, middleware. |
| Models | app/models/ |
ORM entity definitions representing database tables. |
| Schemas | app/schemas/ |
Pydantic models defining API contracts. |
| Services | app/services/ |
Business logic. Framework-independent, testable in isolation. |
| Repositories | app/repositories/ |
Data access primitives. The transaction boundary lives in get_db, not here. |
| Utils | app/utils/ |
Reusable helpers. Promote to a service if they grow too large. |
- Framework: FastAPI
- ORM: SQLAlchemy 2 (async) + asyncpg / aiosqlite
- Validation: Pydantic v2
- Migrations: Alembic (async)
- Server: Uvicorn
- Auth: PyJWT
- Logging: structlog
- Package manager: uv
- Linter/formatter: Ruff
- Type checker: mypy (strict)
- Testing: pytest + httpx + pytest-asyncio + pytest-cov
You need uv installed. Everything else (including Python 3.13) is fetched for you.
# First-time onboarding: installs deps, sets up pre-commit, copies .env.example -> .env
make dev-setup
# Generate a real secret and put it in .env
echo "SECRET_KEY=$(openssl rand -hex 32)" >> .env
# Apply migrations and run the dev server
make migrate
make devVisit http://localhost:8000/docs.
cp .env.example .env
# set SECRET_KEY in .env
docker compose up --buildThe app service waits for Postgres to be healthy, runs alembic upgrade head on startup, and then launches uvicorn on port 8000.
| Target | What it does |
|---|---|
make help |
List all targets |
make install |
uv sync |
make dev-setup |
Install + pre-commit + copy .env.example to .env |
make dev |
Run uvicorn with --reload |
make test |
Run all tests with coverage (80% floor) |
make test-unit / make test-integration |
Run a subset |
make lint |
Ruff check + format check |
make fmt |
Ruff format + safe fixes |
make typecheck |
mypy --strict against app/ |
make migrate |
alembic upgrade head |
make migration m="..." |
Generate an autogenerated revision |
make up |
docker compose up -d --build |
make down |
docker compose down (preserves volumes) |
make nuke |
Wipe this project's containers, network, volumes, and image |
make clean |
Remove caches, coverage, and local DB files |
- Separation of concerns — each layer has a single responsibility
- Testability — business logic is decoupled from the framework and database
- Transactional boundary lives at the session — repositories expose CRUD primitives;
get_dbcommits on success and rolls back on exception - Migrations as source of truth —
Base.metadata.create_allis not called in production; Alembic owns the schema - No placeholder secrets — settings reject
SECRET_KEYvalues containingreplace-withorchange-me
This project includes a CLAUDE.md file that provides AI coding agents (Claude Code, Cursor, Copilot, etc.) with the context they need to make correct changes: architecture rules, directory conventions, common commands, and code style guidelines. When adding new domains, conventions, or non-obvious patterns, update CLAUDE.md so agents stay aligned with the project's expectations.