# Fastapi Best Practices

> Use when scaffolding or refactoring a Python web API: async vs sync handlers, Pydantic v2 schemas, dependency injection, project layout by domain, or production uvicorn/gunicorn workers.

- Skill: `jajabong/fastapi-best-practices` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jajabong/fastapi-best-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jajabong/fastapi-best-practices/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: jajabong (https://skillmd.com/u/jajabong)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/jajabong/fastapi-best-practices

---


# FastAPI Best Practices

Full reference: `/Users/henry/hermes-wiki/concepts/fastapi-best-practices.md` (435 lines, sourced from tiangolo/full-stack-fastapi-template + zhanymkanov/fastapi-best-practices).

## Trigger

Scaffolding or refactoring a FastAPI service: project layout, DI, async/sync, Pydantic v2, deployment, or security.

## Core Rules (from wiki TL;DR)

1. Prefer `async def` for I/O-bound routes; never block the event loop with `time.sleep` or sync SDKs inside async handlers.
2. Use Pydantic v2 with a custom base model, `model_config`, and `field_validator` for all request/response schemas.
3. Organize code by domain inside `src/`, not by file type; keep routers, schemas, models, services, and dependencies colocated per module.
4. Use FastAPI's dependency injection (`Depends`) for reusable request validation, auth, and DB lookups—dependency results are cached per request.
5. For production, run behind a reverse proxy (Traefik/Caddy/nginx) with multiple Uvicorn workers; use a real task queue (Celery/ARQ) for anything longer than a second.

## Audit Checklist (use when reviewing an existing FastAPI app)

- [ ] All routes `async def`? (sync routes run in threadpool — acceptable but prefer async)
- [ ] No `time.sleep` / sync `requests.` / `urllib.request` inside async handlers?
- [ ] Sync SDK calls offloaded via `run_in_threadpool` or `asyncio.to_thread`?
- [ ] Pydantic v2 (not v1): `model_config = ConfigDict(...)`, `field_validator`, `model_validate`?
- [ ] DB sessions scoped per-request via `Depends(get_session)`? No global session?
- [ ] Connection pool sized per worker (`pool_size` 5-10, `pool_pre_ping=True`)?
- [ ] CORS explicit origins (never `*` + credentials)? TrustedHost for internal?
- [ ] Background tasks: `BackgroundTasks` only for <1s fire-and-forget; Celery/ARQ for durable work?
- [ ] Production: gunicorn + uvicorn workers behind reverse proxy? Not raw `uvicorn --workers`?

## Common Pitfalls

- Blocking event loop with sync code in async (symptom: all requests stall)
- Pydantic v1→v2 migration breakpoints (`BaseSettings` → `pydantic_settings`, `validator` → `field_validator`, `orm_mode` → `from_attributes`)
- SQLAlchemy async session lifecycle (never share across requests/tasks)
- OpenAPI schema traps (circular refs, `response_model` double instantiation)

## Verification

```bash
# Import check
PYTHONPATH=src python -c "import <app>"

# Full test suite
python -m pytest tests/ -x -q

# Runtime probe (if app has one)
curl localhost:8000/healthz
```

## Promotion History

- 2026-08-09: stub → full skill after 2 real-task successes:
  1. Audited anchor repo FastAPI gateway (src/anchor/server.py) against checklist
  2. Fixed verify_runtime sync-blocking in async lifespan (src/anchor/lifespan.py:280) via `run_in_threadpool` — commit 9d3d4d9
