A production-minded FastAPI starter that gives you a clean async API, a real database path, authentication, observability, CI checks, and multiple test layers from day one.
| Area | Included |
|---|---|
| API | FastAPI application with health endpoints and protected CRUD routes for entities. |
| Database | PostgreSQL, SQLAlchemy async sessions, Psycopg, and Alembic migrations run as a one-shot startup task before the API starts. |
| Authentication | OAuth2 authorization-code flow, JWT bearer validation, and a Keycloak-backed Docker setup. |
| Validation and settings | Pydantic v2 schemas and pydantic-settings based application, database, and auth configuration. |
| Observability | Structured logging plus Logfire/OpenTelemetry instrumentation for FastAPI and SQLAlchemy. |
| Local runtime | Docker Compose stack for the API, PostgreSQL, and Keycloak. |
| Quality gates | Ruff linting, Ty type checks, import-linter architecture checks, coverage enforcement, SonarCloud analysis, and Snyk security status. |
| Dependency upkeep | Dependabot is configured for Python, Docker, Docker Compose, and GitHub Actions updates. |
This template keeps tests split by purpose so each feedback loop stays clear:
- Unit tests cover isolated settings, auth, service, IO, logging, and exception behavior.
- Integration tests exercise the API and repository with disposable PostgreSQL through Testcontainers.
- Scenario tests live in
scenario-testsand use Behave plus HTTPX against the running Docker Compose stack. - Coverage checks run with
pytest-cov; CI currently enforces at least 80% coverage. - Mutation testing is configured for auth, IO, service, and repository modules, but kept outside the normal PR gate.
At a glance, the template wires requests, authentication, business behavior, persistence, migrations, and scenario tests like this:
flowchart LR
client["Client / API consumer"] -->|"HTTP"| routes["app/routes<br/>FastAPI routers"]
scenario["scenario-tests<br/>Behave + HTTPX"] -->|"HTTP against running stack"| routes
routes -->|"Depends"| auth["app/auth<br/>OAuth2 + JWT validation"]
keycloak["Keycloak<br/>OIDC issuer"] -->|"Tokens + JWKS"| auth
routes -->|"Pydantic input/output"| io["app/io<br/>Request and response schemas"]
routes -->|"Depends"| service["app/service<br/>Business behavior"]
service --> repository["app/repository<br/>Async data access"]
repository --> database["app/database<br/>Engine and sessions"]
model["app/model<br/>SQLAlchemy models"] --> repository
database --> postgres[("PostgreSQL")]
compose["Docker Compose<br/>startup order"] --> alembic["migrate service<br/>Alembic upgrade head"]
alembic --> postgres
alembic --> main["app/main.py<br/>Application startup"]
settings["app/settings<br/>Environment configuration"] -.-> routes
settings -.-> database
settings -.-> auth
The application uses a small layered architecture:
app/routesowns HTTP endpoints, request dependencies, and route grouping.app/serviceholds business behavior and coordinates repository calls.app/repositoryisolates persistence logic behind reusable async repository helpers.app/modeldefines SQLAlchemy database models.app/iodefines request and response schemas at the API boundary.app/settings,app/auth, andapp/loggerkeep cross-cutting concerns separate from endpoint logic.
This shape is meant for teams that want a practical backend template: simple enough to understand quickly, but structured enough to grow into a real service without immediately rewriting the foundation.
uv sync
cp .env.example .envFill in the required values in .env before starting the app.
Development mode:
make runmake run applies alembic upgrade head with the migration dependency group, then starts the FastAPI app.
Docker-based development:
docker compose up --buildDocker Compose builds the API from Dockerfile and the one-shot migration service from Dockerfile.migration. Their default image tags include COMPOSE_PROJECT_NAME, so parallel worktrees do not overwrite each other's local images; APP_IMAGE and MIGRATION_IMAGE can still override those tags. Compose starts migrate first, waits for alembic upgrade head to exit successfully, and then starts the API container. If migrations fail, the API container does not start.
Swagger UI signs users in with the public fastapi-docs client using authorization code flow plus PKCE. The API continues to validate the fastapi-client audience. A browser client must never receive a client secret; OIDC_CLIENT_SECRET is used only by the confidential client in scenario tests.
Compose does not assign host-global container names. To run isolated stacks in parallel, give each one a unique project name, application port, Keycloak port, and public URLs:
COMPOSE_PROJECT_NAME=fastapi-feature-a \
APP_PORT=8081 \
APP_PUBLIC_URL=http://localhost:8081 \
KEYCLOAK_PORT=8181 \
OIDC_PUBLIC_URL=http://localhost:8181 \
docker compose up --buildThe keycloak-config one-shot service registers the exact Swagger callback for APP_PUBLIC_URL; production deployments should likewise use exact redirect URIs rather than wildcards.
When adopting this repository, update the application name, package metadata, image names, OIDC realm/client identifiers, and Sonar/Snyk project identifiers. Compose service names such as db and keycloak are internal DNS names and do not need to be renamed for stack isolation.
Build and push both images before deployment:
- API image: built from
Dockerfile; default command ispython -m app.main; installs only application runtime dependencies. - Migration image: built from
Dockerfile.migration; default command isalembic -c alembic.ini upgrade head; installs only themigrationdependency group and only ships the Alembic migration files.
For ECS, run the migration image as a one-off task before updating or starting the service. For EKS, run the migration image as a Kubernetes Job before rolling out the Deployment. In both cases, the migration task/job must use the same production database environment variables as the application release.
Run the default test suite:
make testRun tests with coverage:
make test-covRun scenario tests against the e2e Docker Compose stack:
export COMPOSE_FILE=docker-compose.yml:docker-compose.e2e.yml
export POSTGRES_PASSWORD=postgres
export KEYCLOAK_ADMIN_PASSWORD=admin
export OIDC_CLIENT_SECRET=change-me
export E2E_PASSWORD=test-password
docker compose up --build --wait
cd scenario-tests
uv sync
OIDC_CLIENT_SECRET=change-me E2E_PASSWORD=test-password uv run behaveRun mutation testing from Linux or WSL because mutmut requires fork support:
uv run --group test --with mutmut mutmut runThe GitHub Actions workflow runs Ruff, Ty, unit tests, integration tests, coverage, Docker image build validation, and scenario tests. Dependabot keeps dependency updates visible, while SonarCloud and Snyk expose code-quality and security status at the top of this README.