Path and query parameters cannot carry structured payloads. Request bodies solve that by letting you send JSON with many fields at once. Pydantic—the validation library FastAPI uses internally—reads Python type hints and enforces shape and constraints. After part 3 taught URL patterns (/items/{item_id} vs. ?q=...), we now let clients send the entire item description in one go and validate it via Pydantic models.
Key terms
- Request body – the JSON payload carried by
POST/PUTrequests; the focus of this post. - Pydantic – the parsing and validation engine powered by Python type hints.
- BaseModel – the parent class every Pydantic model inherits from to gain validation features.
Practice card
- Estimated time: 40 minutes
- Prereqs: Part 3 code, Python 3.12, experience running
uv run fastapi dev- Goal: pair a Pydantic model with a request body and observe the validation flow
Prefer uvicorn main:app --reload if the fastapi dev CLI is unavailable; both commands reach the same /docs page.
Understanding JSON request bodies
- Define request schemas with
BaseModel - Receive JSON and watch FastAPI convert it automatically
- Inspect 422 responses when validation fails
Build a Pydantic model
FastAPI ships with Pydantic support by default. Declare a model and pass it as a parameter to your endpoint.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
in_stock: bool = True
tags: list[str] = []
Pydantic makes it safe to use list/dict defaults (it copies them per request), but plain Python classes usually need `default_factory`. Type hints communicate the expected shape to both FastAPI and the generated OpenAPI schema.
Integrating Pydantic models with FastAPI
Add the model to the route signature and FastAPI handles parsing plus validation.
@app.post("/items", status_code=201)
def create_item(item: Item):
return {"message": "created", "data": item}
FastAPI serializes the returned Pydantic model automatically, so you can return `item`, `item.dict()`, or wrap it inside another object. Here we include a simple status message plus the validated payload.
Swagger UI auto-populates an example JSON payload and displays the field descriptions.
Example: counseling request intake
For a student counseling app, define the payload like this:
class CounselingRequest(BaseModel):
student_id: int
topic: str
preferred_slots: list[str]
@app.post("/counseling")
def create_request(payload: CounselingRequest):
return {"status": "queued", "student_id": payload.student_id}
Frontends built in any language can inspect the autogenerated OpenAPI schema (or the /docs page) to learn the exact JSON structure, because FastAPI exports every Pydantic model as part of its spec.
Test with uv and curl
uv run fastapi dev main.py
Then send a request:
curl -X POST http://127.0.0.1:8000/items \
-H "Content-Type: application/json" \
-d '{"name": "notebook", "price": 3.5, "tags": ["study"]}'
If fastapi dev is missing, use uv run uvicorn main:app --reload. Either way, visit http://127.0.0.1:8000/docs to send the same request through Swagger UI if you prefer clicking over curl. The response body mirrors the model structure shown in your Pydantic class.
Validation and error handling
Missing a required field or sending the wrong type triggers a 422.
{
"detail": [
{
"type": "missing",
"loc": ["body", "item", "name"],
"msg": "Field required"
}
]
}
Swagger shows the same diagnostic message.
422 Unprocessable Entity means FastAPI parsed the JSON successfully but could not validate it against the schema. Pydantic will also try to coerce reasonable inputs (for example, the string "5" into the integer 5); add stricter validators or Strict* field types if you want to forbid coercion.
Nested models and defaults
Pydantic handles nested objects naturally.
class Supplier(BaseModel):
name: str
email: str
class Item(BaseModel):
name: str
price: float
supplier: Supplier | None = None
Send supplier in the request body to receive a structured object; omit it to have FastAPI set None automatically. On Python 3.9 or earlier, write Optional[Supplier] = None instead of the Supplier | None union syntax.
Practice
- Follow along: start the dev server, open
/docs, createItem, POST to/items, and confirm the 201 response. - Extend: add the
Suppliernested model and modify the Swagger example JSON to include{ "supplier": { "name": "Stationery Co", "email": "[email protected]" } }. - Debug: remove a required field or send the wrong type to read the 422 payload.
- Done when: both success and failure cases behave exactly as expected and you can describe the schema verbally.
Wrap-up
To accept structured data, declare the schema with Pydantic and let FastAPI parse plus validate it for you. Next time we will reuse these models to build a small CRUD flow backed by an in-memory list.
💬 댓글
이 글에 대한 의견을 남겨주세요