Request and Response Handling
Explore how FastAPI handles requests and responses, including data validation and serialization.
Content
Handling JSON
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Handling JSON in FastAPI — Your app's emotional support for data
"If REST is a conversation, JSON is the language — and FastAPI is that translator who also brings snacks."
You already know how to wire up routes (Routing and Endpoints) and how to shape responses with response models (Response Models). You also handled multipart blobs like images and file uploads (Form Data and Files). Now we zoom into the most common medium of modern APIs: JSON. This is where data gets structured, validated, and either honored or roasted by Pydantic.
Why JSON handling matters (and why FastAPI makes it joyful)
- Most web clients (browsers, mobile apps, other servers) speak HTTP + JSON.
- Good JSON handling means: validation, clear errors, fast parsing, and correct serialization (dates, IDs, nested objects).
FastAPI automates a ton of this using Python type hints, Pydantic models, and Starlette under the hood. If you've used response_model before, you'll recognize some patterns — now we'll use them for request bodies too.
Quick taxonomy: How you can accept JSON in an endpoint
- Pydantic model (recommended) — explicit schema, validation, docs, examples.
- Typed dicts / typing.Dict[str, Any] — flexible, minimal validation.
- Raw request using
Request— when you need the raw body or custom parsing.
| Strategy | Best for | Pros | Cons |
|---|---|---|---|
| Pydantic model | Structured input with validation | Automatic docs & errors | Must define schema upfront |
| Dict[str, Any] | Flexible payloads | Simple, no model needed | Less validation, less docs |
| Request object | Raw control (e.g., non-JSON or streaming) | Full flexibility | Manual parsing + error handling |
The canonical pattern: request body with Pydantic
This is the one you'll use 90% of the time.
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional, List
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., example="Tasty Cupcake")
description: Optional[str] = None
price: float
tags: List[str] = []
@app.post("/items/", response_model=Item, status_code=201)
async def create_item(item: Item):
# item is already validated and typed
return item
Notes:
- The
item: Itemparameter tells FastAPI to read the JSON body and validate it against the Pydantic model. response_model=Item(you've seen this) ensures the response follows the schema — great for docs and security.- If a field is missing or wrong type, FastAPI returns a neat
422 Unprocessable Entitywith details.
Question: Why not always return the incoming item directly? Because sometimes you want to sanitize or add computed fields before returning.
When JSON is flexible or unknown: Dict[str, Any]
If you don't know the shape in advance (webhooks, generic proxies), accept a plain dict:
from typing import Dict, Any
from fastapi import FastAPI
app = FastAPI()
@app.post('/webhook')
async def webhook(payload: Dict[str, Any]):
# do whatever; but no automatic validation
return {"received": True}
Pro tip: you lose Pydantic's validation and docs. Use sparingly.
Need the raw JSON bytes? Use Request
Sometimes you need the raw bytes (signed payloads, custom parsing):
from fastapi import Request
@app.post('/raw')
async def raw(request: Request):
body_bytes = await request.body()
text = body_bytes.decode('utf-8')
return {"length": len(body_bytes)}
Use this when verifying HMAC signatures or parsing nonstandard JSON.
Extras: advanced JSON handling you’ll love
- Extra fields: Pydantic models default to
ignorefor unknown fields. Useclass Config: extra = 'forbid'to reject unknown fields, orallowto keep them. - Aliases and CamelCase JSON: define
aliason Field or setallow_population_by_field_nameif your API needs different external names. - Datetime and special types: use
jsonable_encoder()to convert Pydantic models (and datetimes, UUIDs, ObjectIds) into JSON-friendly types before manual serialization. - Performance: consider
ORJSONResponsefor very large JSON workloads — it uses orjson under the hood.
Example: forbidding extra fields
class User(BaseModel):
name: str
class Config:
extra = 'forbid'
If extra fields arrive, FastAPI responds with a validation error. Cleaner than mysterious silent drops.
Common gotchas (and how to avoid them)
- Missing Content-Type: if the client doesn't send
Content-Type: application/json, FastAPI may not parse the body as JSON. Always set it. - Expecting form data: JSON and multipart/form-data are different beasts. If you previously accepted files (Form Data and Files), ensure your client sends the right content type.
- Mutable defaults: don't use mutable defaults in Pydantic models for runtime mutation. Use default_factory instead.
- Returning OR returning DB objects: converting ORMs to JSON needs
jsonable_encoder.
Mini-case study: Accepting nested JSON and returning a filtered response
class Owner(BaseModel):
id: int
username: str
class Pet(BaseModel):
name: str
species: str
owner: Owner
@app.post('/pets/', response_model=Pet)
async def add_pet(pet: Pet):
# we might store it, but we only return a subset (the response_model will enforce it)
saved = save_to_db(pet.dict())
return saved
This shows nested models and how response_model ensures the outgoing JSON matches the intended schema — even if your DB adds secrets or extra fields.
Final checklist before you ship JSON endpoints
- Use Pydantic models for predictable input and clear docs.
- Return models or use
response_modelto avoid leaking internals. - Set
Content-Type: application/jsonon clients. - Use
jsonable_encoderfor non-JSON-native Python types. - Consider
extra = 'forbid'if you want strict contracts.
"Validation is not an enemy; it's the bouncer at the club that keeps corrupt data from ruining the party." — Your future bug-free self.
TL;DR (quick recap)
- FastAPI maps JSON bodies to function params using Pydantic models by default — this gives you validation, docs, and nice errors.
- For flexible payloads use
Dict[str, Any]; for raw control useRequest. - Use
response_modelto control outgoing JSON (you saw this earlier in Response Models). - Remember the Content-Type and difference between JSON vs form-data (you handled files earlier).
Go implement a tiny POST endpoint right now — then try sending bad JSON and watch FastAPI politely explain what you did wrong. It’s the kind of roasting that actually helps.
Version note: This builds directly on routing, response models, and form/file handling — next up: great practices for pagination and error formats (spoiler: one uniform shape to rule them all).
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!