Files
gh-christianlouis-inboxconv…/docs/adr/003-fastapi-web-framework.md
T

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:

  1. High-concurrency API requests (multiple users, simultaneous email polls)
  2. Asynchronous database and network I/O without blocking
  3. Automatic input validation and serialization
  4. Built-in interactive API documentation
  5. 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:

  1. Native Async Support: First-class async/await support matches our async database (asyncpg) and mail protocol (aiosmtplib, aioimaplib) libraries
  2. Automatic Validation: Pydantic models provide request/response validation with no extra code
  3. Auto-generated Docs: OpenAPI spec and Swagger UI at /api/docs out of the box
  4. Type Safety: Python type annotations drive both validation and editor tooling
  5. Performance: Comparable to Node.js and Go for async I/O workloads (Starlette/uvicorn underneath)
  6. Dependency Injection: Built-in Depends() system for auth, database sessions, and config
  7. 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
  • See ADR-004 for database choice (asyncpg/SQLAlchemy async)
  • See ADR-007 for JWT authentication via Depends()

References