Template de API REST com autenticação JWT, rate limiting, observabilidade e arquitetura modular. Pronto para produção.
| Tecnologia | Versão | Por quê |
|---|---|---|
| Python | 3.14 | uuid.uuid7 nativo (UUIDs ordenados sem lib externa) |
| FastAPI | ≥0.115 | Performance, tipagem nativa, OpenAPI automático |
| SQLAlchemy | ≥2.0 | ORM maduro com AsyncSession — queries não bloqueiam o event loop |
| asyncpg | ≥0.30 | Driver PostgreSQL nativo async (mais rápido que psycopg2) |
| Alembic | ≥1.13 | Migrações de schema versionadas |
| PostgreSQL | — | Banco principal; Neon como provider cloud |
| Redis | ≥5.0 | Refresh tokens opacos + contadores de rate limit |
| bcrypt | ≥4.0 | Hash de senha com salt automático |
| PyJWT | ≥2.8 | Geração e validação de JWT |
| slowapi | ≥0.1.9 | Rate limiting por IP via decorador |
| Sentry SDK | ≥2.0 | Captura de erros em produção com tracing |
| prometheus-fastapi-instrumentator | ≥7.0 | Métricas HTTP expostas em /metrics |
| python-json-logger | ≥3.0 | Logs estruturados em JSON |
| pydantic-settings | ≥2.0 | Configuração via variáveis de ambiente tipada |
| Docker + Compose | — | Ambiente reproduzível com Redis embutido |
app/
├── core/
│ ├── config.py # Settings via .env (pydantic-settings)
│ ├── database.py # Engine PostgreSQL + get_db dependency
│ ├── exceptions.py # Exceções de domínio + handlers HTTP
│ ├── middleware.py # Correlation ID, security headers, X-Response-Time
│ ├── logging_config.py # JSON logging com request_id injetado
│ └── context.py # ContextVar para request_id entre camadas
├── models/
│ └── user.py # Modelo SQLAlchemy (UUIDv7, soft delete)
├── modules/
│ ├── auth/ # Login, refresh, logout
│ │ ├── router.py
│ │ ├── service.py # Lógica de autenticação + account lockout
│ │ └── schema.py
│ ├── user/ # CRUD de usuários
│ │ ├── router.py
│ │ ├── service.py
│ │ ├── repository.py
│ │ └── schema.py
│ └── health/ # Health check
└── security/
├── jwt.py # create_access_token / decode_token
├── refresh.py # Refresh tokens opacos no Redis
├── dependencies.py # get_current_user / require_roles
└── limiter.py # Instância global do slowapi Limiter
Padrão por módulo: router → service → repository
- Router: recebe requisição, valida input, delega ao service
- Service: lógica de negócio, levanta exceções de domínio
- Repository: acesso ao banco, sem lógica de negócio
Dois tokens complementares:
Access Token (JWT, 15 min)
- Stateless — verificado localmente sem tocar o banco ou Redis
- Contém apenas o
user_idno camposub - Curto para minimizar janela de abuso caso seja vazado
Refresh Token (opaco, 30 dias)
- UUID4 armazenado no Redis com TTL
- Rotacionado a cada uso (o token anterior é invalidado)
- Permite logout real e revogação imediata
Account Lockout
- Após
LOGIN_MAX_ATTEMPTSfalhas consecutivas, a conta é bloqueada porLOGIN_LOCKOUT_SECONDS - Implementado com
INCR+EXPIREno Redis — sem tabela extra no banco
| Recurso | Implementação |
|---|---|
| Senha hasheada | bcrypt com salt automático |
| RBAC | require_roles(UserRole.ADMIN) como Depends |
| Soft delete | is_active=False em vez de DELETE — preserva integridade referencial |
| Security headers | X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy, Permissions-Policy |
| Rate limiting | slowapi — 5/min no login, 200/min global |
| CORS | Configurável via CORS_ORIGINS no .env |
| Docs desabilitadas | /docs, /redoc e /openapi.json só existem com DEBUG=true |
Os IDs usam uuid.uuid7 (Python 3.14 stdlib).
UUIDv4 é aleatório — inserções em B-tree causam fragmentação e page splits.
UUIDv7 é time-ordered (primeiros 48 bits = timestamp ms) — inserções são sequenciais, índice cresce como um BIGSERIAL mas com unicidade global.
Logs (JSON estruturado)
{"timestamp": "...", "level": "INFO", "logger": "app.core.middleware",
"message": "POST /v1/auth/login | status=200 duration_ms=42.1",
"request_id": "3e4a1b2c-..."}Cada requisição gera um X-Request-ID (ou usa o header recebido) propagado via ContextVar para todos os logs da requisição.
Métricas (Prometheus + Grafana Cloud)
Endpoint /metrics exposto automaticamente com contadores, histogramas de latência e status codes por rota.
Para visualizar no Grafana Cloud (free tier):
- Crie uma conta em grafana.com
- Vá em Connections → Add new connection → Prometheus
- Aponte o scrape URL para
https://sua-api.com/metrics - Importe o dashboard FastAPI Observability (ID
[16110](https://grafana.com/grafana/dashboards/16110))
Em dev local, o endpoint fica em http://localhost:8000/metrics.
Sentry
Erros não tratados são capturados com stack trace e enviados ao Sentry. Configurado com traces_sample_rate=0.2 (20% das transações para performance tracing).
Copie .env.example para .env e preencha:
APP_NAME=Minha API
DEBUG=false
DB_HOST=seu-host.neon.tech
DB_PORT=5432
DB_USER=usuario
DB_PASSWORD=senha
DB_NAME=nome_do_banco
DB_SSL=true
SECRET_KEY=troque-em-producao
ACCESS_TOKEN_EXPIRE_MINUTES=15
REFRESH_TOKEN_EXPIRE_DAYS=30
CORS_ORIGINS=["https://meufront.com"]
REDIS_URL=redis://localhost:6379
SENTRY_DSN=https://...@sentry.io/...
LOGIN_MAX_ATTEMPTS=5
LOGIN_LOCKOUT_SECONDS=300docker compose up --buildO Redis sobe junto. O container da API aguarda o Redis estar saudável antes de iniciar (healthcheck + condition: service_healthy).
pip install -r requirements-dev.txt
uvicorn main:app --reload| Método | Rota | Auth | Descrição |
|---|---|---|---|
GET |
/ |
— | Versão da API |
GET |
/v1/health |
— | Health check |
POST |
/v1/auth/login |
— | Login (rate limit: 5/min) |
POST |
/v1/auth/refresh |
— | Rotaciona refresh token |
POST |
/v1/auth/logout |
— | Revoga refresh token |
GET |
/v1/users/me |
Bearer | Usuário autenticado |
GET |
/v1/users |
Admin | Lista usuários (paginado) |
GET |
/v1/users/{id} |
Admin | Busca usuário por ID |
POST |
/v1/users |
Admin | Cria usuário |
PATCH |
/v1/users/{id} |
Admin | Atualiza usuário |
DELETE |
/v1/users/{id} |
Admin | Desativa usuário (soft delete) |
GET |
/metrics |
— | Métricas Prometheus |
- Crie
app/modules/meumodulo/comrouter.py,service.py,repository.py,schema.py - Adicione o modelo em
app/models/ - Registre o router em
main.py:app.include_router(meumodulo_router, prefix=API_PREFIX)
- Gere a migration:
alembic revision --autogenerate -m "add meumodulo" alembic upgrade head
Todos os métodos de repositório e service devem ser async def. Use await ao chamar operações de banco (await self.db.execute(...), await self.db.commit()).