Cheatsheet tracing a request end-to-end.
High-level flow
1. ASGI server (Uvicorn) accepts HTTP, parses to ASGI scope.
2. FastAPI middleware stack runs (CORS, GZip, custom).
3. Router matches path; finds handler.
4. Dependencies resolved (DI graph).
5. Pydantic validates path / query / body / headers.
6. Handler coroutine executes.
- SQLAlchemy session acquired from pool.
- Queries executed (potentially via repository).
- Domain objects mutated.
- Session committed.
7. Return value processed:
- response_model (Pydantic) shapes / filters.
- Serializer encodes to JSON.
8. Response middleware (reverse order).
9. ASGI server sends bytes.
10. Background tasks fire (after response sent).
Concrete example
POST /users with {"email":"[email protected]","password":"..."}:
# 1. ASGI: parses HTTP, builds scope.
# 2. Middleware: CORS, request_id, tracing.
# 3. Router: matches POST /users → create_user handler.
# 4. Deps: get_db opens a session.
# 5. Pydantic: UserCreate validated.
# 6. Handler:
async def create_user(data: UserCreate, db: AsyncSession = Depends(get_db)):
if await db.scalar(select(User).where(User.email == data.email)):
raise HTTPException(409, "email taken")
user = User(email=data.email, hashed_password=hash_pw(data.password))
db.add(user)
await db.commit()
await db.refresh(user)
return user
# 7. response_model=UserRead → Pydantic shapes:
# - reads via from_attributes
# - omits hashed_password
# - serializes to JSON
# 8. Middleware exit (request_id header set, trace finished).
# 9. ASGI writes bytes.
Each layer’s responsibility
| Layer | What it does |
|---|---|
| ASGI | HTTP bytes ↔ Python objects |
| FastAPI middleware | Cross-cutting (CORS, auth, logging) |
| Router | Path → handler matching |
| Depends | Per-request resources (db, user, settings) |
| Pydantic (in) | Type / shape validation; constraints |
| Handler | Business logic |
| SQLAlchemy | Persistence; queries |
| Pydantic (out) | response_model shaping; serialization |
Where things go wrong
| Symptom | Likely layer |
|---|---|
| 422 with detail | Pydantic input validation |
| 500 internal error | Handler or DB |
| Slow response | DB (N+1, missing index) |
| Wrong fields in response | Missing response_model or wrong shape |
| Connection refused on DB | Pool exhausted; PgBouncer; pool_pre_ping |
| Stale data | Session expire / replica lag |
Concurrency
Per request:
- One coroutine.
- One DB session (one pool connection while in use).
- Multiple concurrent requests share the pool.
For parallel work within a request: TaskGroup; separate sessions.
async with asyncio.TaskGroup() as tg:
async with AsyncSessionLocal() as s1, AsyncSessionLocal() as s2:
a = tg.create_task(s1.scalar(...))
b = tg.create_task(s2.scalar(...))
Authentication / authorization
Token from Authorization header
→ oauth2 dep extracts string
→ current_user dep validates + queries User
→ handler receives User instance
Each layer can reject (401, 403).
Background tasks
@app.post("/signup")
async def signup(data: UserIn, bg: BackgroundTasks, db = Depends(get_db)):
user = await create_user(db, data)
bg.add_task(send_welcome_email, user.email)
return user
send_welcome_email runs after response is sent (same process).
Error path
try:
# handler logic
except IntegrityError as e:
await db.rollback()
raise HTTPException(409, "conflict")
except ValueError as e:
raise HTTPException(400, str(e))
Pydantic ValidationError caught by FastAPI’s RequestValidationError handler → 422 (or custom 400).
Session boundaries
async def get_db():
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit() # auto-commit on success
except Exception:
await session.rollback()
raise
Or explicit commit in handlers.
Cross-request lifespan resources
@asynccontextmanager
async def lifespan(app):
app.state.engine = create_async_engine(...)
app.state.http = httpx.AsyncClient(...)
app.state.redis = await aioredis.from_url(...)
yield
await app.state.engine.dispose()
await app.state.http.aclose()
await app.state.redis.close()
App-wide; per-request via request.app.state.*.
Read this next
If you want a sequence diagram of this flow, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .