FastAPI Request Body and JSON Data
When a client sends data to your API — like creating a new user or placing an order — that data travels inside the request body. FastAPI reads this body, validates every field, and hands your function a clean Python object ready to use.
Where Request Body Fits in a Request
HTTP Request
┌─────────────────────────────┐
│ Method: POST │
│ URL: /users │
│ Headers: Content-Type: JSON │
│ │
│ Body: │
│ { │
│ "name": "Arjun", │
│ "email": "a@example.com", │
│ "age": 28 │
│ } │
└─────────────────────────────┘
│
▼
[ FastAPI ]
reads body, validates fields
Define the Shape of Your Data with Pydantic
FastAPI uses Pydantic models to describe what the body must look like. A Pydantic model is a Python class that inherits from BaseModel:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
email: str
age: int
Each class attribute is a field. The type hint tells FastAPI what type each field must be.
Use the Model in a POST Route
@app.post("/users")
def create_user(user: User):
return {
"message": "User created",
"name": user.name,
"email": user.email,
"age": user.age
}
The function argument user: User tells FastAPI to read the body as a User object. FastAPI validates it before your function runs.
What Happens During Validation
Client sends body:
{
"name": "Arjun",
"email": "a@example.com",
"age": 28
}
FastAPI checks:
✓ name is present and is a string
✓ email is present and is a string
✓ age is present and is an integer
All pass → user object created → function runs
Client sends invalid body:
{
"name": "Arjun",
"age": "twenty-eight" ← wrong type
}
FastAPI checks:
✓ name is OK
✗ email is MISSING
✗ age is not an integer
Result: 422 Unprocessable Entity
with a clear list of what went wrong
Optional Fields with Defaults
Not every field needs to be required. Give it a default value to make it optional:
from typing import Optional
class Product(BaseModel):
name: str
price: float
description: Optional[str] = None ← optional, defaults to None
in_stock: bool = True ← optional, defaults to True
Minimum valid body:
{"name": "Keyboard", "price": 49.99}
FastAPI fills in:
description = None
in_stock = True
Accessing Body Fields Inside Your Function
The validated model object behaves like a regular Python object:
@app.post("/products")
def create_product(product: Product):
# Access fields with dot notation
name = product.name
price = product.price
# Convert to dict when needed
product_dict = product.dict()
return product_dict
Combining Body with Path and Query Parameters
@app.put("/shops/{shop_id}/products")
def update_product(
shop_id: int, ← from path
notify: bool = False, ← from query
product: Product = ... ← from body
):
return {
"shop": shop_id,
"notify": notify,
"product": product.dict()
}
FastAPI sorts them by location: shop_id → path (inside URL) notify → query (after ?) product → body (JSON payload)
Key Points
- Request body data is sent as JSON inside a POST, PUT, or PATCH request.
- Define the expected shape using a Pydantic
BaseModelclass. - FastAPI validates every field automatically before running your function.
- Invalid data returns a
422error with a clear description. - Use
Optional[type] = Noneor a default value for non-required fields.
