zapEngineering

FastAPI

Async-first APIs: dependency injection, Pydantic validation, async correctness, and production serving.

1 item

Links1

01NotesNote

When to reach for FastAPI

Async-first APIs, lots of I/O fan-out (calling other services, DBs, LLM APIs), and anything where you want automatic OpenAPI docs and Pydantic validation at the edge. It's my default for new standalone services and for AI-facing backends.

Dependency injection is the core idea

Depends() is how you wire in DB sessions, the current user, settings, rate limiters, etc. Dependencies are resolved per-request and can be cached, nested, and overridden in tests.

async def get_db() -> AsyncSession:
    async with SessionLocal() as session:
        yield session

@app.get("/clients/{id}")
async def read_client(id: int, db: AsyncSession = Depends(get_db)):
    return await client_service.get(db, id)

Override dependencies in tests with app.dependency_overrides — no monkeypatching needed.

Pydantic v2 at the boundary

  • Define explicit request and response models. Use response_model= so you never leak internal fields (password hashes, soft-delete flags) by accident.
  • Pydantic v2 is significantly faster (Rust core). Use model_config for things like from_attributes=True to read straight from ORM objects.

Async pitfalls specific to FastAPI

  • A sync def endpoint runs in a threadpool (fine, but limited threads). An async def endpoint runs on the event loop — never block it.
  • Use an async DB driver (asyncpg via SQLAlchemy async, or async Tortoise) in async endpoints. A sync driver inside async def silently serializes all requests.
  • BackgroundTasks is fine for fire-and-forget work that's cheap and non-critical (sending an email). For anything that must complete or must survive a crash, use a real queue or Temporal — not background tasks.

Production serving

  • uvicorn workers behind gunicorn (or uvicorn --workers), fronted by Nginx. Set worker count to roughly 2 × cores for I/O-bound loads, then tune by measurement.
  • Add /health (liveness) and /ready (readiness — checks DB/Redis) endpoints for orchestrators.