Skip to content

FastAPI — Production Patterns

Project Structure

app/
├── main.py              # FastAPI app, lifespan, router includes
├── config.py            # Pydantic Settings
├── database.py          # engine, session factory, get_db
├── models/              # SQLAlchemy ORM models
├── schemas/             # Pydantic request/response models
├── api/                 # routers (thin HTTP layer)
├── services/            # business logic
└── repositories/        # DB access layer
Layer Responsibility
api/ HTTP only — parse request, call service, return response
services/ Business logic, orchestration, validation rules
repositories/ Database queries, ORM operations
schemas/ Input/output contracts (Pydantic)
models/ DB table definitions (SQLAlchemy)

Configuration with Pydantic Settings

from functools import lru_cache

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")

    app_name: str = "My Service"
    debug: bool = False
    database_url: str
    secret_key: str
    access_token_expire_minutes: int = 30


@lru_cache
def get_settings() -> Settings:
    return Settings()

@lru_cache ensures settings are loaded once. Inject via Depends(get_settings).


Background Tasks

from fastapi import BackgroundTasks, FastAPI

app = FastAPI()


async def send_welcome_email(email: str):
    ...


@app.post("/users")
async def create_user(email: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(send_welcome_email, email)
    return {"message": "User created, email will be sent"}
Use case Tool
Quick fire-and-forget BackgroundTasks (built-in)
Heavy/long-running jobs Celery, ARQ, or Dramatiq
Scheduled tasks APScheduler or Celery Beat

WebSocket

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()


class ConnectionManager:
    def __init__(self):
        self.active: list[WebSocket] = []

    async def connect(self, ws: WebSocket):
        await ws.accept()
        self.active.append(ws)

    def disconnect(self, ws: WebSocket):
        self.active.remove(ws)

    async def broadcast(self, message: str):
        for conn in self.active:
            await conn.send_text(message)


manager = ConnectionManager()


@app.websocket("/ws/{room}")
async def websocket_endpoint(ws: WebSocket, room: str):
    await manager.connect(ws)
    try:
        while True:
            data = await ws.receive_text()
            await manager.broadcast(f"[{room}] {data}")
    except WebSocketDisconnect:
        manager.disconnect(ws)

Error Handling & Logging

import logging

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

logger = logging.getLogger("app")


@app.exception_handler(RequestValidationError)
async def validation_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(status_code=422, content={"error": "validation", "detail": exc.errors()})


@app.middleware("http")
async def log_requests(request: Request, call_next):
    logger.info("→ %s %s", request.method, request.url.path)
    response = await call_next(request)
    logger.info("← %s %d", request.url.path, response.status_code)
    return response

Health Checks

from fastapi import Depends, FastAPI
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

app = FastAPI()

# get_db is an app dependency that yields AsyncSession.
@app.get("/health")
async def liveness():
    return {"status": "ok"}


@app.get("/health/ready")
async def readiness(db: AsyncSession = Depends(get_db)):
    await db.execute(text("SELECT 1"))
    return {"status": "ready"}
Endpoint Purpose
/health Liveness — is the process running?
/health/ready Readiness — are dependencies available?

Deployment

FROM python:3.13-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev
COPY app/ app/
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
services:
  api:
    build: .
    ports: ["8000:8000"]
    env_file: [.env]
    depends_on: { db: { condition: service_healthy } }
  db:
    image: postgres:17
    environment: { POSTGRES_DB: mydb, POSTGRES_USER: user, POSTGRES_PASSWORD: pass }
    healthcheck: { test: ["CMD-SHELL", "pg_isready -U user"], interval: 5s, retries: 5 }
Setting Dev Production
--reload Yes No
--workers 1 CPU cores × 2 + 1
--log-level debug info
Reverse proxy None Nginx / Traefik
HTTPS Optional Required