This template provides a robust starting point for building APIs with FastAPI. It includes user authentication, CRUD operations, JWT token-based authentication, and PostgreSQL integration. The setup is streamlined with Docker and includes comprehensive documentation and testing tools.
- 🔐 User authentication with basic login and Google Auth
- 👥 User management with creation and CRUD operations
- 📄 Example endpoints for Posts, Users, and Votes
- 💓 API healthcheck endpoint
- 🔑 JWT token-based authentication
- ⚙️ Middleware support
- 🌎 CORS configuration
- 📝 Comprehensive Swagger API documentation
- 🐘 PostgreSQL database integration
- 🔒 Field encryption for sensitive data
Important
Min Python version: 3.14
Clone this repo:
git clone https://github.com/ezeparziale/fastapi-api-templateCreate virtual environment:
python -m venv envActivate environment:
- Windows:
. env/scripts/activate- Mac/Linux:
. env/bin/activateUpgrade pip:
python -m pip install --upgrade pipInstall requirements:
pip install -r requirements-dev.txtInstall pre-commit:
pre-commit installCreate .env file. Check the example .env.example
🌐 Google Auth credentials:
Create your app and obtain your client_id and secret:
https://developers.google.com/workspace/guides/create-credentials🔒 How to create a secret key:
openssl rand -base64 64🔐 How to create an encryption key:
To create an encryption key for securing sensitive data, you can use the generate_key.py script provided in the repository. Run the following command:
python generate_key.pyThis will generate a secure encryption key.
🚧 Before first run:
Run docker-compose 🐳 to start the database server
docker compose -f "compose.yaml" up -d --build adminer dband init the database with alembic:
alembic upgrade head🔑 Create a self-signed certificate with openssl:
openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365uvicorn app.main:app --reload --port 8000 --env-file .env --ssl-keyfile key.pem --ssl-certfile cert.pem--env-file .env is what puts OTEL_* in the environment, the monitoring stack needs it.
Run linter and formatter
scripts/lint.shscripts/format.shRun coverage
coverage run -m pytestcoverage report --show-missingcoverage htmlOr run all in one with:
scripts/coverage.shRun pytest with coverage
coverage run -m pytestor
scripts/test.shAlembic is used for database migrations. Below are some common commands to manage your database schema.
To autogenerate a new revision based on the changes detected in your models, run:
alembic revision --autogenerate -m "your message here"To create a blank revision for custom migrations, run:
alembic revision -m "your message here"To apply the latest migrations and upgrade the database schema, run:
alembic upgrade headTo revert the last migration and downgrade the database schema, run:
alembic downgrade -1After creating a revision, you can edit the generated script to define your custom migrations.
Traces, metrics and logs with Grafana, Tempo, Prometheus, Loki and Alloy.
FastAPI has OpenTelemetry built in, so the app only needs OTEL_EXPORTER_OTLP_ENDPOINT
in .env. It sends OTLP to Alloy, and Alloy routes each signal to its store:
app ──OTLP────> alloy ──traces──> tempo ──remote_write (span + service graph metrics)──> prometheus
stdout ──> alloy ──metrics─> prometheus
└──logs───> loki
grafana reads tempo + prometheus + loki
-
Start the stack:
docker compose up -d alloy tempo prometheus loki grafana
-
Open Grafana on http://localhost:3000 with
admin/admin. The data sources and the dashboards are provisioned automatically. -
Send some traffic so there is something to look at:
curl http://localhost:8000/health
-
Explore the provisioned dashboards:
Overview / Golden Signals: traffic, errors, latency and saturation, the entry pointApp Logs: log lines plus the share of logs per levelRequest Metrics: throughput, latency percentiles and error rateAPM / Service Overview: service graph, span RED metrics and recent traces
Or query the data sources by hand from Grafana > Explore:
Tempofor traces,Prometheusfor metrics,Lokifor logs.
📊 Useful endpoints:
| Service | URL |
|---|---|
| Grafana | http://localhost:3000 |
| Alloy UI | http://localhost:12345 |
| Prometheus | http://localhost:9090 |
| Tempo | http://localhost:3200 |
| Loki | http://localhost:3100 |
🔎 How the dashboards work:
The app writes one JSON object per line (loguru with serialize=True), so the
level is nested at record.level.name and the message at record.message. The
logs panel queries use | json, and Loki exposes those nested fields flattened
with underscores, which is where record_level_name comes from.
Request Metrics is built on http.server.request.duration, the histogram
FastAPI records for every request. Percentiles come from
histogram_quantile() over the buckets, and error rate is the share of 5xx
responses. Two details worth knowing:
- Paths that match no route, such as a
404, carry nohttp_routelabel, so they are grouped asunmatchedwithlabel_replace(). - Both error rate panels read
0%while healthy instead of showing no data, so a quiet dashboard never looks broken.
Overview / Golden Signals is the single pane over the four signals. Traffic,
errors and latency come from http.server.request.duration and saturation from
http.server.active_requests, the in-flight request gauge FastAPI exports. The
bottom row repeats the golden signals from the trace side
(traces_spanmetrics_*) so both pipelines are visible side by side, and the
dashboard header links through to Request Metrics and APM / Service Overview
for the drill-down.
APM / Service Overview is built on the metrics the Tempo metrics-generator
derives from the traces, not on the app's own HTTP histogram. The generator is
enabled in tempo/tempo.yaml (the metrics_generator block plus the
overrides.defaults.metrics_generator.processors list) and remote-writes two
families of series back into Prometheus:
traces_spanmetrics_*— RED metrics per span (calls_total,latency_bucket,size_total), labeled byservice,span_name,span_kindandstatus_code.traces_service_graph_*— per-edge request counts, failures and durations between aclientand aserver, which draw the node graph.
Two things are worth knowing:
- The Service Graph panel is a Tempo query (
queryType: serviceMap), not a PromQL panel. It resolves thetraces_service_graph_*series through the Prometheus data source linked underserviceMapin the Tempo data source. - Prometheus needs
--web.enable-remote-write-receiverfor the generator to push into it; without that flag the generator logs remote-write failures and the dashboard stays empty.
global.scrape_interval, so the Prometheus data source declares
timeInterval: 60s. That matters because $__rate_interval is derived from it:
at the 15s default it lands near 1m, which is too short to hold two samples, and
every rate() in the dashboards returns nothing. Leave it in sync if you ever
change the receiver cadence.
🛠️ Configuration files:
observability/
├── docker/
│ ├── alloy/config.alloy # OTLP and Docker logs in, traces, metrics and logs out
│ ├── prometheus/prometheus.yml
│ ├── tempo/tempo.yaml # storage + metrics-generator (span metrics, service graph)
│ └── loki/loki-config.yaml
└── grafana/
├── dashboards/
│ ├── golden-signals-overview.json # traffic, errors, latency, saturation
│ ├── app-logs.json # log lines and share per level
│ ├── request-metrics.json # throughput, latency percentiles, error rate
│ └── apm-service-overview.json # service graph, span RED metrics, traces
└── provisioning/
├── dashboards/dashboards.yaml
└── datasources/datasources.yaml