FastAPI Essentials
Build production APIs with FastAPI: typed path, query and body parameters validated by Pydantic, response models, dependency injection with Depends, async vs sync endpoints, and project structure. The exercises rebuild FastAPI's routing and DI with the standard library.
🎯 What you'll learn
- Typed parameters, request bodies and automatic 422s
- Response models and OpenAPI docs
- Dependency injection with Depends and yield
- async def vs def, routers, lifespan and deployment
📚 Before you start
1Why FastAPI
FastAPI builds HTTP APIs from type-hinted Python functions: it validates requests with Pydantic, serializes responses, generates OpenAPI docs (/docs) automatically, and runs on ASGI servers (uvicorn) with first-class async support.
from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel, Field
app = FastAPI()
class ItemIn(BaseModel):
name: str = Field(min_length=1)
price: float = Field(gt=0)
class ItemOut(ItemIn):
id: int
@app.post("/items", response_model=ItemOut, status_code=status.HTTP_201_CREATED)
async def create_item(item: ItemIn, repo: "ItemRepo" = Depends(get_repo)):
return await repo.add(item)
@app.get("/items/{item_id}", response_model=ItemOut)
async def get_item(item_id: int, repo: "ItemRepo" = Depends(get_repo)):
item = await repo.get(item_id)
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return item- Path parameters (
item_id: int) are parsed and validated from the URL; query parameters are other simple-typed arguments; a Pydantic model argument is the JSON body. - Invalid input returns 422 with field-level errors automatically.
response_modelfilters and validates output, so internal fields never leak.
2Dependency Injection with Depends
Dependencies are functions (sync, async, or generators) declared as parameter defaults. FastAPI resolves them per request, caches each dependency within a request, and supports sub-dependencies:
async def get_db():
async with SessionLocal() as session: # setup
yield session # injected value
# teardown after the response (session closed)
async def get_current_user(token: str = Depends(oauth2_scheme), db = Depends(get_db)):
user = await authenticate(db, token)
if not user:
raise HTTPException(401, "Invalid token", headers={"WWW-Authenticate": "Bearer"})
return userUse dependencies for DB sessions, authentication, pagination parameters and settings. Override them in tests with app.dependency_overrides[get_db] = fake_db.
3async def vs def
async defendpoints run on the event loop: only use non-blocking libraries inside (httpx.AsyncClient,asyncpg, async SQLAlchemy).- Plain
defendpoints run in a thread pool, so blocking libraries (requests, sync DB drivers) are fine there. - A blocking call inside
async deffreezes every request on that worker.
4Structure and Production Concerns
- Split into routers (
APIRouter(prefix="/orders", tags=["orders"])) per feature; keep business logic in services, not in endpoints. - Configuration via
pydantic-settings(env vars); one place for settings, injected withDepends. - Lifespan events (
@asynccontextmanagerlifespan) to create and close connection pools. - Middleware for CORS, request IDs, timing; exception handlers for consistent error bodies.
- Run with several workers (
uvicorn --workers 4or gunicorn with uvicorn workers) behind a reverse proxy; background work goes to a task queue (Celery, arq, RQ), notBackgroundTasksfor anything important. - Test with
TestClient(sync) orhttpx.AsyncClientwithASGITransport.
⚠️ Common Pitfalls
- Blocking calls in
async defendpoints. - Returning ORM objects without
response_model, leaking fields. - Creating a DB connection per request without pooling.
- Business logic inside endpoints, making it untestable without HTTP.
- Heavy work in
BackgroundTasks: it runs in the same process and is lost on restart.
📋 Quick Reference
path params (typed) · query params · Pydantic body · 422 on validation errors · response_model filters output
Depends: per-request, cached, sub-dependencies, yield for setup/teardown · dependency_overrides in tests
async def → only non-blocking I/O · def → thread pool · routers per feature · lifespan for pools
multiple workers behind a proxy · real task queue for background jobs · /docs from OpenAPI🧠 Key takeaways
- Type hints drive parsing, validation (422) and OpenAPI docs.
- response_model filters output so internal fields never leak.
- Depends resolves per request, caches, nests and supports yield cleanup; override in tests.
- Only non-blocking I/O in
async def; use real task queues for background work.
🎤 Interview Q&A
Q1How does FastAPI validate requests?core▾
From type hints: path and query parameters are converted and validated, Pydantic model parameters are parsed from the JSON body, and failures return 422 with field-level details. Response models validate and filter output.
- Where do the docs come from? The generated OpenAPI schema at /docs and /redoc.
Q2How does Depends work?core▾
Parameters with Depends(fn) are resolved per request: FastAPI calls fn (resolving its own dependencies), caches results within the request, injects the value, and for yield-dependencies runs cleanup after the response.
- Use cases? DB sessions, current user/auth, settings, pagination.
Q3async def or def for endpoints?core▾
async def for code using async I/O libraries (runs on the event loop); def for blocking code (runs in a thread pool). Never call blocking functions inside async def.
- CPU-heavy work? Offload to a process pool or task queue.
Q4How would you structure a larger FastAPI project?senior▾
Routers per feature, schemas (Pydantic) separate from ORM models, services for business logic, repositories for data access, settings via pydantic-settings, lifespan for pools, middleware for cross-cutting concerns, and tests with dependency overrides.
- Why separate schemas from ORM models? API contracts evolve independently of storage.
Q5How do you deploy FastAPI?core▾
Run uvicorn (or gunicorn with uvicorn workers) with several worker processes in a container behind a reverse proxy/load balancer, with health checks, timeouts, structured logging and OpenTelemetry; background jobs in Celery/arq/RQ.
- Workers count? Typically ~CPU cores for async apps; measure.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.
int receives 'abc'. FastAPI returns…