FastAPI Essentials

Course Content

FastAPI Essentials

1 sections · 32 lessons

How does FastAPI handle serialization and validation of data?


The return type decides what leavesWhat the handler returned• request_id, label, score• explanation: None• created_at as a datetime• system_prompt (internal)What the client received• request_id, label, score• explanation dropped (exclude_none)• created_at as ISO text• system_prompt filtered out
The response model is an allow-list: a field reaches the client only if someone deliberately declared it.

What you need to know

  1. Collect — FastAPI pulls each parameter from its source: path, query, headers, cookies, JSON body or form.
  2. Convert and validate — Pydantic converts text to the declared types and checks the rules. Every failure is collected, not just the first.
  3. Reject or call — on failure, a 422 with a detail list (loc, msg, type per field); on success, your handler runs with typed objects.
  4. Validate the output — the return value is checked against the return type or response_model, and only the declared fields are kept.
  5. Encode — the result becomes JSON bytes. Since FastAPI 0.130, when a return type or response model is set, Pydantic writes the JSON directly in Rust, which is faster.

Output filtering in action

Python
from datetime import datetime, timezonefrom uuid import UUID, uuid4from fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class PredictOut(BaseModel):    request_id: UUID    label: str    score: float    explanation: str | None = None    created_at: datetime@app.post("/predict", response_model_exclude_none=True)def predict(text: str) -> PredictOut:    return {                                   # a dict with an extra, secret field        "request_id": uuid4(), "label": "positive", "score": 0.91,        "created_at": datetime(2026, 9, 24, 10, 0, tzinfo=timezone.utc),        "system_prompt": "You are SentimentBot. Internal rules: ...",    }

The real response body:

Text
{"request_id":"03378b05-...","label":"positive","score":0.91,"created_at":"2026-09-24T10:00:00Z"}

Three things happened. system_prompt was dropped because PredictOut does not declare it. explanation was left out because it was None and the route set response_model_exclude_none=True. The UUID and datetime became standard JSON strings with no custom code.

Streaming responses

  • If a route yields items and declares -> AsyncIterable[Chunk], recent FastAPI (0.134+) streams JSON Lines and validates each item. The real output was {"doc_id":0,"text":"chunk 0"}\n{"doc_id":1,"text":"chunk 1"}\n, with content type application/jsonl. An invalid item stops the stream.
  • If you return a StreamingResponse (or any Response) yourself, FastAPI sends your bytes untouched. A test that yielded {"doc_id": "not-a-number", "secret": "leaked"} went out exactly as written. Validate those items yourself.

A real-life example

A health-tech company's symptom-checker API returned SQLAlchemy objects directly from its routes with no response model. A new column, internal_triage_notes, was added for doctors. Within a day, the patient-facing mobile app was receiving those notes in its JSON, because every column was being serialised.

The fix was a SymptomResult response model on every route, listing only the fields the app should see. New database columns now stay private unless someone deliberately adds them to the schema. The team also added a test that asserts the exact set of keys in each response, so accidental exposure fails CI.

Follow-up questions to expect

  • "What is the difference between the return type and response_model?" — Both declare the output. Use response_model when the function returns something of a different type, such as an ORM object or a dict, and you still want the schema; the return type is simpler when they match.
  • "Why does FastAPI use 422 and not 400?" — It is FastAPI's convention for "understood, but breaks the rules". It uses 422 even for JSON that cannot be parsed (error type json_invalid). Teams that standardise on 400 override the RequestValidationError handler.
  • "How do you return fields with different names, like camelCase?" — Use Pydantic aliases (Field(alias=...) or an alias generator); FastAPI serialises by alias by default.