Request Body — Pydantic Models In
BeginnerDeclare a Pydantic BaseModel parameter and FastAPI parses the JSON body into a typed object — wrong shapes get a field-by-field 422 before your code runs, and the model doubles as the docs schema.
Overview
The request body is where FastAPI + Pydantic earn their reputation. Define the payload once as a BaseModel subclass; a parameter of that type makes FastAPI read the JSON body, validate every field, convert types, and hand your function a real object with attribute access and editor autocomplete. Missing fields, wrong types, and extra nesting all produce a 422 listing each problem with its exact location — an error format your frontend team will thank you for. Path, query, and body parameters combine freely in one signature, and PATCH-style partial updates use optional fields with exclude_unset. Never use payload: dict in real code — you lose validation, docs, and autocomplete in one stroke.
BaseModel In — Parsed, Validated, Typed
A model parameter (no default) binds to the JSON body. Inside the function you have an object, not a dict: order.amount is a float, guaranteed. model_dump() converts back to a dict when needed.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class OrderIn(BaseModel):
customer_name: str
phone: str
amount: float # rupees
upi_id: str | None = None # optional
cod: bool = False # default
@app.post("/orders")
def create_order(order: OrderIn): # ← body, because it's a BaseModel
fee = 0 if order.amount >= 499 else 40
return {
"status": "created",
"payable": order.amount + fee,
"payment": "COD" if order.cod else (order.upi_id or "card"),
"echo": order.model_dump(), # model → dict
}
# Valid request:
# POST /orders {"customer_name": "Asha", "phone": "9876543210", "amount": 599}
# → 200, payable 599, payment "card"
# Invalid request: {"customer_name": "Asha", "amount": "five hundred"}
# → 422 with BOTH problems, precisely located:
# [{"loc": ["body", "phone"], "msg": "Field required"},
# {"loc": ["body", "amount"], "msg": "Input should be a valid number"}]Path + Query + Body Together, and Partial Updates
FastAPI sorts a mixed signature automatically: route names → path, BaseModel → body, the rest → query. For PATCH, make fields optional and read only what the client actually sent with exclude_unset.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class OrderUpdate(BaseModel): # all optional → partial update
amount: float | None = None
upi_id: str | None = None
cod: bool | None = None
# PATCH /orders/42?notify=true body: {"cod": true}
@app.patch("/orders/{order_id}")
def update_order(
order_id: int, # path (matches route)
patch: OrderUpdate, # body (BaseModel)
notify: bool = False, # query (plain type + default)
):
changes = patch.model_dump(exclude_unset=True)
# {"cod": True} ← ONLY what the client sent, not None-filled fields
return {"order_id": order_id, "applied": changes, "notify": notify}
# Why exclude_unset matters: without it you cannot tell
# "client sent upi_id: null (clear it)" from "client did not send upi_id".
# Two bodies? Wrap both — FastAPI nests them by parameter name:
class Address(BaseModel):
city: str
pincode: str
@app.post("/shipments")
def ship(order: OrderUpdate, address: Address):
# expects {"order": {...}, "address": {...}}
return {"city": address.city}Key Points to Remember
- 1BaseModel parameter = JSON body: parsed, validated, typed — never use dict
- 2422 errors list every bad field with its exact location (loc)
- 3Path + query + body mix freely in one signature; FastAPI sorts by type
- 4model_dump(exclude_unset=True) distinguishes "not sent" from "sent as null"
Interview Questions
Sign in to ask AriaWhy is payload: dict considered bad practice for FastAPI request bodies?
Implement PATCH correctly — how do you apply only the fields the client sent?
A signature has an int, a BaseModel, and a str with default — where does each bind from?
Ask Aria about Request Body — Pydantic Models In
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.