Production-ready FastAPI gateway for service discovery, secure routing, API keys, rate limiting, caching, resilience, and observability.
Overview β’ Features β’ Screenshots β’ Architecture β’ Quick Start β’ API Flow β’ Observability β’ Troubleshooting
Distributed API Gateway is a portfolio-grade platform engineering project that demonstrates how microservice organizations centralize traffic management, authentication, service discovery, request routing, reliability, and observability behind a single controlled entry point.
The project includes a real FastAPI gateway, PostgreSQL-backed service and route registry, Redis-backed rate limiting and caching, API-key and JWT authentication, retry handling, circuit breaker protection, audit logs, Prometheus metrics, Grafana dashboards, Docker Compose, Kubernetes manifests, GitHub Actions, Jenkins, and a bundled mock downstream microservice for end-to-end validation.
|
|
|
|
|
|
flowchart TD
A[Client / API Consumer] --> B[FastAPI Distributed API Gateway]
B --> C[Request Context Middleware]
C --> D[Route Lookup]
D --> E[(PostgreSQL Service + Route Registry)]
D --> F[Authentication Layer]
F --> G[API Key / JWT Validation]
G --> H[Redis Rate Limiter]
H --> I[Redis Cache]
I --> J[Retry + Circuit Breaker]
J --> K[Mock User Microservice]
B --> L[(PostgreSQL Audit Logs)]
B --> M[Prometheus Metrics]
M --> N[Grafana Dashboard]
Client sends request to /gateway/{path}
β
Gateway normalizes incoming path
β
Route registry resolves matching path_prefix and HTTP method
β
If auth_required=true, gateway validates x-api-key or Authorization: Bearer token
β
Redis enforces client + route rate limit
β
Redis cache is checked for cacheable GET requests
β
Gateway builds upstream URL using service.base_url + upstream_path_prefix + path suffix
β
Request is proxied to the downstream service with timeout + retry handling
β
Circuit breaker records success/failure
β
Response is cached if eligible
β
Audit log is written to PostgreSQL
β
Prometheus metrics are updated
β
Gateway returns downstream response to the client
| Gateway Request | Route Config | Downstream Call |
|---|---|---|
GET /gateway/health |
path_prefix="", upstream_path_prefix="", method=ANY |
GET http://mock-user-service:9001/health |
GET /gateway/health with valid x-api-key |
auth_required=true |
Gateway validates API key before proxying |
GET /gateway/health repeated multiple times |
cache_enabled=true, cache_ttl_seconds=30 |
Response may be served from Redis cache |
erDiagram
SERVICES ||--o{ ROUTES : owns
CLIENTS ||--o{ API_KEYS : owns
CLIENTS ||--o{ AUDIT_LOGS : generates
SERVICES {
string id
string name
string base_url
string health_check_path
string status
datetime created_at
}
ROUTES {
string id
string service_id
string path_prefix
string upstream_path_prefix
string method
boolean auth_required
boolean cache_enabled
integer cache_ttl_seconds
integer timeout_seconds
integer retry_count
integer circuit_failure_threshold
}
CLIENTS {
string id
string name
string role
boolean active
datetime created_at
}
API_KEYS {
string id
string client_id
string key_prefix
string hashed_key
boolean active
datetime created_at
}
AUDIT_LOGS {
string id
string client_id
string request_id
string route
string method
integer status_code
datetime created_at
}
| Requirement | Version |
|---|---|
| Docker Desktop | Latest stable |
| Python | 3.11+ |
| Git | Any recent version |
cp .env.example .env
docker compose down -v --remove-orphans
docker compose up --buildOpen:
Gateway Console: http://localhost:8000/dashboard
Swagger API Docs: http://localhost:8000/docs
Health Check: http://localhost:8000/health
Mock Service: http://localhost:8081/health
Prometheus: http://localhost:9090
Grafana: http://localhost:3000
Grafana login:
admin / admin
bash sample-data/bootstrap.shThe script creates:
- downstream service registry entry
- authenticated
/usersgateway route - demo client
- API key
- first successful
/gateway/usersrequest
Then generate more traffic:
export API_KEY="PASTE_GENERATED_API_KEY"
bash sample-data/generate-traffic.shcurl -X POST http://localhost:8000/admin/services \
-H "x-admin-key: dev-admin-key" \
-H "Content-Type: application/json" \
-d '{
"name": "mock-user-service",
"base_url": "http://mock-user-service:9001",
"health_check_path": "/health"
}'Use the returned service ID from step 1.
curl -X POST http://localhost:8000/admin/routes \
-H "x-admin-key: dev-admin-key" \
-H "Content-Type: application/json" \
-d '{
"service_id": "SERVICE_ID",
"path_prefix": "/users",
"upstream_path_prefix": "/users",
"method": "ANY",
"auth_required": true,
"cache_enabled": true,
"cache_ttl_seconds": 30,
"timeout_seconds": 3,
"retry_count": 1,
"circuit_failure_threshold": 3
}'curl -X POST http://localhost:8000/admin/clients \
-H "x-admin-key: dev-admin-key" \
-H "Content-Type: application/json" \
-d '{"name":"demo-client","role":"consumer"}'curl -X POST http://localhost:8000/admin/api-keys \
-H "x-admin-key: dev-admin-key" \
-H "Content-Type: application/json" \
-d '{"client_id":"CLIENT_ID"}'Store the returned api_key. It is shown only once.
curl -X GET http://localhost:8000/gateway/users \
-H "x-api-key: YOUR_GENERATED_API_KEY"Expected response:
{
"service": "mock-user-service",
"data": [
{"id": "usr_001", "name": "Ava Patel", "role": "consumer"},
{"id": "usr_002", "name": "Noah Singh", "role": "admin"}
]
}The gateway endpoints expose x-api-key and Authorization header fields directly in Swagger.
When testing /gateway/{path} in Swagger, enter the path without the leading slash:
users
Do not enter:
/users
Swagger encodes a leading slash as %2F, which changes the route path.
Open:
http://localhost:9090/targets
The target should be:
http://gateway:8000/metrics UP
Important: gateway:8000 is a Docker-internal hostname. It works from Prometheus inside Docker, but not from your browser on the host machine. In your browser, use:
http://localhost:8000/metrics
Useful Prometheus queries:
gateway_requests_total
rate(gateway_requests_total[5m])
sum(rate(gateway_requests_total[1m]))
histogram_quantile(0.95, sum(rate(gateway_request_latency_seconds_bucket[5m])) by (le))
gateway_cache_events_total
gateway_rate_limit_blocks_total
gateway_downstream_errors_total
Open:
http://localhost:3000
Then go to:
Dashboards β API Gateway β Distributed API Gateway Overview
Some panels may show No data until the related event actually happens:
| Panel | Why It May Be Empty | How to Generate Data |
|---|---|---|
| Gateway Requests | No traffic yet | Call /health, /ready, /gateway/users |
| Latency p95 | Not enough traffic yet | Run repeated requests |
| Rate Limit Blocks | Limit has not been exceeded | Send more requests than the route limit |
| Downstream Errors | Mock service is healthy | Stop mock service or route to invalid upstream |
Generate traffic:
export API_KEY="PASTE_GENERATED_API_KEY"
bash sample-data/generate-traffic.shpip install -r requirements.txt
pytest
coverage run -m pytest && coverage report
ruff check app tests
bandit -r app -x testsValidated quality checks:
pytest: passing
ruff: passing
bandit: passing
docker compose up --buildkubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.example.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/hpa.yamlRoute 53 / CloudFront / ALB
β
AWS EKS or ECS Fargate
β
Distributed API Gateway Pods
β
Amazon RDS PostgreSQL
Amazon ElastiCache Redis
Amazon ECR
CloudWatch Logs
Prometheus + Grafana
AWS Secrets Manager
|
|
| Skill Area | Demonstrated Through |
|---|---|
| Backend Engineering | FastAPI routing, middleware, proxying, validation |
| Distributed Systems | Gateway routing, retries, circuit breaker, rate limiting |
| Platform Engineering | Service registry, route registry, client onboarding |
| Security Engineering | API keys, JWT, admin controls, hashed secrets |
| Data Engineering | PostgreSQL schema, audit logs, operational data model |
| DevOps | Docker, Kubernetes, GitHub Actions, Jenkins |
| Observability | Prometheus metrics, Grafana dashboard, request IDs |
| Cloud Architecture | AWS-ready deployment design with RDS and ElastiCache |
| Testing | Unit and integration-style gateway tests |
π Folder Structure
distributed-api-gateway/
βββ app/
β βββ api/ # health, auth, admin, gateway routes
β βββ core/ # config, security, metrics, logging
β βββ db/ # SQLAlchemy session/bootstrap
β βββ middleware/ # request context + metrics middleware
β βββ models/ # database entities
β βββ schemas/ # Pydantic DTOs
β βββ services/ # router, Redis, rate limiter, circuit breaker
β βββ main.py # FastAPI app + dashboard
βββ mock_services/ # downstream user microservice for demo traffic
βββ docs/
β βββ screenshots/ # README screenshots
β βββ api-spec.md
β βββ aws-architecture.md
β βββ security.md
βββ k8s/ # Kubernetes manifests
βββ monitoring/ # Prometheus + Grafana provisioning
βββ sample-data/ # bootstrap and traffic scripts
βββ tests/ # unit and integration-style tests
βββ .github/workflows/ci.yml # GitHub Actions pipeline
βββ Dockerfile
βββ docker-compose.yml
βββ Jenkinsfile
βββ Makefile
βββ requirements.txt
βββ README.md
Gateway returns Example Domain or 404 from Cloudflare
That means the gateway is working but the registered service points to https://example.com or another placeholder upstream.
Register the included Docker Compose mock service instead:
{
"name": "mock-user-service",
"base_url": "http://mock-user-service:9001",
"health_check_path": "/health"
}Then create a route with:
{
"path_prefix": "/users",
"upstream_path_prefix": "/users",
"method": "ANY"
}Swagger gateway route says βValid API key or JWT requiredβ
The route requires authentication.
Use the x-api-key field shown in the Swagger gateway endpoint after clicking Try it out.
Also enter the path without a leading slash:
users
Not:
/users
I clicked Prometheus target `gateway:8000` and browser cannot open it
That is expected.
gateway is a Docker Compose service name and only resolves inside the Docker network.
Use this in your browser instead:
http://localhost:8000/metrics
Grafana shows No data
Generate traffic first:
export API_KEY="PASTE_GENERATED_API_KEY"
bash sample-data/generate-traffic.shRate-limit and downstream-error panels remain empty until those events occur.
Docker Compose starts Prometheus/Grafana but not Swagger
Check gateway logs:
docker compose logs gatewayThen restart cleanly:
docker compose down -v --remove-orphans
docker compose up --buildPostgreSQL or Redis connection errors on startup
The app includes startup retries, but a clean volume reset usually fixes stale local state:
docker compose down -v --remove-orphans
docker compose up --build| Priority | Improvement |
|---|---|
| High | Add OpenTelemetry distributed tracing |
| High | Add Redis-backed distributed circuit breaker state |
| High | Add OAuth2/OIDC identity provider integration |
| Medium | Add admin dashboard CRUD controls for services/routes |
| Medium | Add per-client quota policies |
| Medium | Add route-level transformation plugins |
| Medium | Add service health polling and automatic status updates |
| Low | Add Terraform AWS deployment module |
| Low | Add canary deployment strategy |
This project is licensed under the MIT License.
This project is built for portfolio, learning, and authorized internal platform engineering use cases.
Do not expose development credentials, local API keys, or test secrets in public environments.










