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 BaseModel class.
  • FastAPI validates every field automatically before running your function.
  • Invalid data returns a 422 error with a clear description.
  • Use Optional[type] = None or a default value for non-required fields.

Leave a Comment

Your email address will not be published. Required fields are marked *