A production-ready FastAPI microservice template with async database access, multi-tenant isolation, and comprehensive testing.
- Async-only API and data access
- SQLModel models with UUID primary keys and timezone-aware timestamps
- Alembic migrations with drift detection in tests
- Postgres 18 (dev/test), asyncpg driver
- ECS JSON logging via
logging.yaml /health,/ping, and/metricsendpoints- Pagination via
fastapi-pagination - Local Kubernetes dev with k3d + DevSpace
- Non-root container image
- Python 3.13
- uv
- Docker
- k3d, kubectl, and DevSpace (for the default local dev flow)
Use Copier to generate a new project:
# Install Copier
pipx install copier
# Generate project with defaults
copier copy gh:mattwwarren/fastapi-template --vcs-ref copier my-project
# Generate with custom options
copier copy gh:mattwwarren/fastapi-template --vcs-ref copier my-project \
--data project_name="My API Service" \
--data auth_enabled=true \
--data auth_provider=ory| Variable | Type | Default | Description |
|---|---|---|---|
project_name |
string | required | Project name (e.g., "User Auth Service") |
project_slug |
string | auto | Python package name (lowercase with underscores) |
description |
string | "A FastAPI microservice" | Brief project description |
port |
int | 8000 | Development server port |
auth_enabled |
bool | false | Enable authentication middleware |
auth_provider |
choice | none | Auth provider: none, ory, auth0, keycloak, cognito |
multi_tenant |
bool | true | Enable multi-tenant isolation |
storage_provider |
choice | local | Storage: local, s3, azure, gcs |
cors_origins |
string | "http://localhost:3000" | CORS allowed origins |
enable_metrics |
bool | true | Enable Prometheus /metrics endpoint |
enable_activity_logging |
bool | true | Enable audit trail logging |
"Invalid template" error:
- Ensure you're using
--vcs-ref copierto pull from the template branch
Generated code has syntax errors:
- Report issue at https://github.com/mattwwarren/fastapi-template/issues
Missing template variables:
- Run
copier copywithout--defaultsto see all prompts
uv sync --dev
cp .env.example .env
uv run alembic upgrade head
uv run uvicorn fastapi_template.main:app --reload --log-config fastapi_template/core/logging.yamlOpenAPI docs:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
This is the default workflow for local Kubernetes:
devspace build
devspace dev -p devDevSpace defaults live in devspace.yaml and can be overridden via env vars:
export CLUSTER_NAME=fastapi_template
export NAMESPACE=warren-enterprises-ltd
export IMAGE_NAME=fastapi_template
export IMAGE_TAG=dev
export APP_NAME=fastapi_template
export ENVIRONMENT=dev
export LOG_LEVEL=info
export POSTGRES_DB=app
export POSTGRES_USER=app
export POSTGRES_PASSWORD=appdevspace run alembic-revision -- "initial"
devspace run alembic-upgrade
devspace run k3d-downConfiguration is driven by environment variables (see .env.example).
Key values:
DATABASE_URL: async SQLAlchemy URL, e.g.postgresql+asyncpg://app:app@localhost:5432/appAPP_NAME,ENVIRONMENTENABLE_METRICS,SQLALCHEMY_ECHO- Pagination defaults:
PAGINATION_PAGE_SIZE,PAGINATION_PAGE_SIZE_MAX
Logging is configured only via fastapi_template/core/logging.yaml and is ECS JSON.
The runtime entrypoint (scripts/start.sh) uses --log-config and does not
apply its own log level overrides.
If you want a different verbosity, update fastapi_template/core/logging.yaml (or provide an
alternate log config at startup).
- All tables use UUID primary keys with
gen_random_uuid()defaults. created_atandupdated_atare timezone-aware.updated_atis maintained by a Postgres trigger, not by ORM code.
Alembic autogenerate uses SQLModel.metadata. The module fastapi_template/db/base.py
imports every model that should be included in migrations. When you add a new
SQLModel table, also add it to fastapi_template/db/base.py so Alembic can detect it.
alembic upgrade headNever hand-write migrations. Always use:
alembic revision --autogenerate -m "your message"init_db() is test-only; production and local dev should always run Alembic.
GET /healthGET /pingGET /organizationsPOST /organizationsGET /organizations/{organization_id}PATCH /organizations/{organization_id}DELETE /organizations/{organization_id}GET /usersPOST /usersGET /users/{user_id}PATCH /users/{user_id}DELETE /users/{user_id}GET /membershipsPOST /membershipsDELETE /memberships/{membership_id}GET /metrics(internal-only via infra)
List endpoints use fastapi-pagination and return Page responses with stable
ordering by created_at.
/healthvalidates DB connectivity with a short timeout and returns 503 on failure./metricsexports Prometheus metrics (intended for internal networking only).
Tests use pytest-docker to launch Postgres (tests/docker-compose.yml) and
pytest-alembic to ensure schema drift is caught.
Always run tests from the project root (where pyproject.toml is located):
# From project root - pytest-docker starts Postgres automatically
uv run pytestRun with coverage:
uv run pytest --covRun specific test file:
uv run pytest fastapi_template/tests/test_health.pypytest-docker will automatically:
- Start a Postgres container from
tests/docker-compose.yml - Wait for it to be ready
- Run migrations via Alembic
- Clean up after tests finish
"No 'script_location' key found in configuration"
→ You're running pytest from the wrong directory. Run from project root where pyproject.toml is located.
"Cannot connect to Docker daemon"
→ Docker is not running. Start Docker and try again. pytest-docker requires Docker to launch the database container.
uv run ruff check
uv run mypy fastapi_template
uv run pre-commit run --all-filesfastapi_template/main.pyFastAPI appfastapi_template/api/routersfastapi_template/models/SQLModel models and schemasfastapi_template/services/CRUD helpersfastapi_template/db/async engine/session + Alembic model registryalembic/migrationsk8s/Kubernetes manifestsdevspace.yamlDevSpace configuration
Build the full docs locally:
uv run sphinx-build -b html docs docs/_build/htmlKey docs live in docs/:
docs/conventions.rstdocs/howto_add_feature.rstdocs/troubleshooting.rstdocs/decision_log.rstdocs/architecture.rst
- Async-only API + DB access.
- SQLModel + Alembic for schema lifecycle.
- UUID primary keys; DB-managed
updated_at. - ECS JSON logging from
logging.yaml. - k3d + DevSpace for local Kubernetes.
- Create the SQLModel table + schemas in
fastapi_template/models/. - Import the model in
fastapi_template/db/base.pyso Alembic sees it. - Generate a migration via
alembic revision --autogenerate. - Add service helpers in
fastapi_template/services/. - Add endpoints in
fastapi_template/api/. - Add tests under
fastapi_template/tests/.
- Missing tables in tests: ensure the model is imported in
fastapi_template/db/base.py. - DevSpace can't find pods: check image selector and run
devspace build. - Logs look wrong:
fastapi_template/core/logging.yamlis the single source of truth.
- Authentication is intentionally excluded. Use a separate auth service (e.g. Ory) and integrate via API gateway or shared identity flow.
- Kubernetes manifests are currently static for Postgres credentials; update
k8s/postgres-secret.yamlif you change these values.