Backend Overview¶
This guide covers the FastAPI backend for CO2 emissions tracking. Use it to set up your local environment, understand the API structure, and deploy to production. The backend uses PostgreSQL for data persistence, OIDC for authentication, and in-process asyncio.create_task chains with a 10-second safety-net poller for background jobs (see ADR-010 and ADR-016).
Read this if you need to set up the backend locally, understand how the API works, or deploy to production.
For deeper architectural details, see:
- Architecture - Layer patterns
- File Structure - Code organization
- Request Flow - Request lifecycle
- Integration Testing - Test strategy and CI cadence
Quick Start¶
Start all services with Docker Compose:
cd backend
make docker-up
The API runs at http://localhost:8000. View interactive docs at http://localhost:8000/docs.
Stop services with:
make docker-down
Local Development Without Docker¶
Install dependencies and configure your environment:
make install
cp .env.example .env
# Edit .env: set DB_URL, SECRET_KEY, OIDC_* variables
Run migrations and start the server:
make db-migrate
make run
Requires Python 3.12+ and PostgreSQL 15+.
Architecture Overview¶
The backend uses a layered architecture with clear separation of concerns. Each layer handles one responsibility:
| Layer | Location | Purpose |
|---|---|---|
| API | app/api/ | HTTP routing, request handling |
| Service | app/services/ | Business logic, authorization |
| Repository | app/repositories/ | Database queries |
| Models | app/models/ | SQLAlchemy ORM schemas |
| Schemas | app/schemas/ | Pydantic validation |
Authorization happens in the service layer. Repositories are policy-agnostic and receive filters from services.
See Architecture for complete layer details.
Key API Endpoints¶
Visit http://localhost:8000/docs for the complete OpenAPI specification. Main endpoint groups:
Laboratory Management
GET /api/v1/labs- List labsPOST /api/v1/labs- Create labPUT /api/v1/labs/{id}- Update labDELETE /api/v1/labs/{id}- Delete lab
Data Import
POST /api/v1/labs/{id}/imports- Upload CSVGET /api/v1/imports/{id}/status- Check status
Reporting
GET /api/v1/reports/{lab_id}- Generate report
Development Workflow¶
Running Tests¶
Run all tests with coverage:
make test
pytest tests/test_resources.py -v # Specific file
Coverage reports appear in htmlcov/index.html.
Code Quality¶
Format and lint your code:
make format # Format with Ruff
make lint # Check with ruff + mypy
Database Migrations¶
Manage schema changes with Alembic:
make db-revision message="Add user preferences"
make db-migrate # Apply migrations
make db-downgrade # Rollback one step
Configuration¶
Copy .env.example to .env and set these variables:
# Database
DB_URL=postgresql://user:pass@localhost:5432/co2calculator
# Security (generate: openssl rand -hex 32)
SECRET_KEY=your-secret-key-here
ACCESS_TOKEN_EXPIRE_MINUTES=30
# OAuth/OIDC (see backend/.env.example for full set + Keycloak variant)
OAUTH_ISSUER_URL=https://login.microsoftonline.com/{tenant}/v2.0
OAUTH_CLIENT_ID=your-client-id
OAUTH_CLIENT_SECRET=your-client-secret
# CORS
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
See Auth Flow for how the OAuth/OIDC settings are used.
For production: set DEBUG=false, use strong SECRET_KEY, and restrict CORS_ORIGINS to your frontend domain.
Background Processing¶
Background jobs run in-process via asyncio.create_task chains with a 10-second safety-net poller. See ADR-010 for the decision and ADR-016 for the interactive-vs-bulk write strategy.
Jobs cover CSV import processing, emission calculations, factor sync, and report generation.
Authorization¶
In-code RBAC, not OPA. Service methods receive the authenticated user and apply role + scope filters before reaching the repository. See Permission System.
Security Features¶
- Input Validation: Pydantic schemas on all endpoints
- SQL Injection Prevention: SQLAlchemy ORM with parameterized queries
- XSS Prevention: JSON-only responses
- Rate Limiting: Configurable per endpoint
- Authentication Required: All endpoints except
/healthand/docs
CSRF protection is not needed (stateless JWT, no cookies).
Troubleshooting¶
Database Connection Issues¶
Check if PostgreSQL is running:
docker-compose ps postgres
docker-compose logs postgres
psql $DB_URL -c "SELECT 1;"
Background Task Failures¶
Background jobs run inside the web pods. Inspect them via the backend logs and the data_ingestion_jobs table in PostgreSQL:
docker-compose logs backend
psql $DB_URL -c "SELECT id, target_type, state, locked_by, updated_at FROM data_ingestion_jobs ORDER BY updated_at DESC LIMIT 20;"
See ADR-010 for the claim / poller model.
Enable Debug Logging¶
Set in .env:
LOG_LEVEL=DEBUG
DEBUG=true
View logs:
docker-compose logs -f backend
Production Deployment¶
Critical checklist:
- Set
DEBUG=false - Generate secure
SECRET_KEY:openssl rand -hex 32 - Rotate database credentials regularly
- Restrict
CORS_ORIGINSto your frontend domain - Use Gunicorn with Uvicorn workers:
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker
Performance Optimization¶
Applied optimizations:
- Connection Pooling: SQLAlchemy pool (size: 20, overflow: 10)
- Query Optimization: Eager loading to avoid N+1 queries
- Async Operations: FastAPI async endpoints for I/O tasks
- Background Jobs: in-process
asyncio.create_taskchains with a 10s safety-net poller (see ADR-010)
Next Steps¶
For New Developers¶
- Complete Quick Start above
- Read Architecture for layer patterns
- Study Request Flow for request lifecycle
- Explore File Structure for navigation
For API Consumers¶
- Start server:
make docker-up - Open http://localhost:8000/docs
- Test endpoints with Swagger UI
External Documentation¶
Summary¶
FastAPI backend with layered architecture. Start with make docker-up, explore API at /docs. Authorization in service layer, database queries in repositories. Background jobs run in-process per ADR-010. See Architecture for details.