Engineering Notes
Home/Languages/Python/FastAPI Essentials
Languages · Python

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.

💻 Languages● Intermediate⏱ 3 min read🛠️ 2 exercises🎤 5 interview Q&As

🎯 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

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.

python
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_model filters 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:

python
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 user

Use 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 def endpoints run on the event loop: only use non-blocking libraries inside (httpx.AsyncClient, asyncpg, async SQLAlchemy).
  • Plain def endpoints run in a thread pool, so blocking libraries (requests, sync DB drivers) are fine there.
  • A blocking call inside async def freezes 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 with Depends.
  • Lifespan events (@asynccontextmanager lifespan) to create and close connection pools.
  • Middleware for CORS, request IDs, timing; exception handlers for consistent error bodies.
  • Run with several workers (uvicorn --workers 4 or gunicorn with uvicorn workers) behind a reverse proxy; background work goes to a task queue (Celery, arq, RQ), not BackgroundTasks for anything important.
  • Test with TestClient (sync) or httpx.AsyncClient with ASGITransport.

⚠️ Common Pitfalls

  1. Blocking calls in async def endpoints.
  2. Returning ORM objects without response_model, leaking fields.
  3. Creating a DB connection per request without pooling.
  4. Business logic inside endpoints, making it untestable without HTTP.
  5. Heavy work in BackgroundTasks: it runs in the same process and is lost on restart.

📋 Quick Reference

text
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

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

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.

Follow-ups interviewers ask
  • 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.

Follow-ups interviewers ask
  • 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.

Follow-ups interviewers ask
  • 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.

Follow-ups interviewers ask
  • 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.

Follow-ups interviewers ask
  • 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.

1. A path param typed int receives 'abc'. FastAPI returns…
Validation error.
2. Where do blocking DB drivers belong?
Don't block the loop.
3. What does a yield dependency enable?
e.g. DB sessions.
4. How do you replace a dependency in tests?
Built-in override mechanism.