92d67369e9
Agent-Logs-Url: https://github.com/christianlouis/pop_puller_to_gmail/sessions/82f2f361-3513-44e6-991b-db1a19902772 Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
3.9 KiB
3.9 KiB
ADR 003: Use FastAPI as Web Framework
Status: Accepted
Date: 2026-01-25
Deciders: Development Team
Context
The multi-tenant SaaS backend requires a Python web framework that can handle:
- High-concurrency API requests (multiple users, simultaneous email polls)
- Asynchronous database and network I/O without blocking
- Automatic input validation and serialization
- Built-in interactive API documentation
- Easy integration with the async ecosystem (asyncpg, aiosmtplib, aioimaplib)
Decision
We will use FastAPI as the primary web framework for the backend API.
Alternatives Considered
1. Django REST Framework (DRF)
- Pros: Mature ecosystem, batteries-included ORM, admin panel, well-known
- Cons: Synchronous-first, heavier footprint, complex async support, more boilerplate
2. Flask
- Pros: Lightweight, flexible, large community
- Cons: No native async support, requires extensions for validation, no auto-docs, more manual wiring
3. Starlette (bare)
- Pros: Minimal, pure async, very fast
- Cons: No built-in validation, no auto-documentation, requires writing more boilerplate
4. Tornado
- Pros: Battle-tested async framework, good WebSocket support
- Cons: Older API design, less active development, no modern type annotation support
Rationale
FastAPI was chosen because:
- Native Async Support: First-class
async/awaitsupport matches our async database (asyncpg) and mail protocol (aiosmtplib, aioimaplib) libraries - Automatic Validation: Pydantic models provide request/response validation with no extra code
- Auto-generated Docs: OpenAPI spec and Swagger UI at
/api/docsout of the box - Type Safety: Python type annotations drive both validation and editor tooling
- Performance: Comparable to Node.js and Go for async I/O workloads (Starlette/uvicorn underneath)
- Dependency Injection: Built-in
Depends()system for auth, database sessions, and config - Modern Python: Designed for Python 3.8+ with full typing support
Implementation Notes
# Application factory pattern with lifespan context manager
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: create tables, seed defaults
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
# Shutdown: cleanup
app = FastAPI(
title="InboxConverge API",
lifespan=lifespan,
openapi_url="/api/openapi.json",
docs_url="/api/docs",
)
# Versioned API routing
from fastapi import APIRouter
api_router = APIRouter(prefix="/api/v1")
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
api_router.include_router(mail_accounts.router, prefix="/mail-accounts", tags=["mail-accounts"])
Consequences
Positive
- Clean, self-documenting API with zero extra work
- Async I/O throughout eliminates thread-pool bottlenecks
- Pydantic validation catches bad input before it reaches business logic
- Dependency injection decouples auth, DB, and config from route handlers
- Fast iteration: type errors and validation errors caught at startup
Negative
- Smaller ecosystem than Django (fewer ready-made plugins)
- Pydantic v2 migration required breaking changes from v1
- Lifespan/startup patterns require careful structuring to avoid import cycles
Neutral
- Uvicorn required as ASGI server for production
- Gunicorn can be used to manage multiple uvicorn workers
Related Decisions
- See ADR-004 for database choice (asyncpg/SQLAlchemy async)
- See ADR-007 for JWT authentication via
Depends()