Home/Learn/FastAPI/Request Body — Pydantic Models In

Request Body — Pydantic Models In

Beginner
Fundamentals

Declare 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.

One model: parsing + validation + autocomplete + docs schema
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.

Mixed signatures sort themselves; exclude_unset powers PATCH
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 Aria
1

Why is payload: dict considered bad practice for FastAPI request bodies?

EasyPaytm
2

Implement PATCH correctly — how do you apply only the fields the client sent?

MediumSwiggy
3

A signature has an int, a BaseModel, and a str with default — where does each bind from?

MediumAmazon

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.

Loading discussion…