Skip to content

06 Request Body Pydantic

DodaTech 4 min read

title: Pydantic Request Bodies in FastAPI REST APIs weight: 16 date: 2026-06-28 lastmod: 2026-06-28 description: Master Pydantic models for FastAPI request bodies with field validation, nested models, custom validators, and configuration for automatic request parsing and validation. tags: [api-development, fastapi]


Pydantic models define request body schemas in FastAPI using Python type hints with field validation, default values, nested models, custom validators, and automatic JSON parsing, generating OpenAPI schemas automatically.

```mermaid
flowchart TD
  A[Pydantic Model] --> B[Field Types]
  A --> C[Validation]
  A --> D[Nested Models]
  B --> E[str, int, float, bool]
  B --> F[EmailStr, HttpUrl]
  C --> G[field_validator]
  C --> H[Field constraints]
  D --> I[Model as field type]
  style A fill:#e1f5fe
  style B fill:#c8e6c9
  style C fill:#fff9c4

Define a Pydantic model class inheriting from BaseModel with type-annotated fields. FastAPI receives JSON data, validates it against the model, and injects the validated instance into the route handler. Invalid data returns 422 with field-level error details.

Think of Pydantic models like a tax form. The form has specific fields (name, income), types (text, number), and rules (income must be positive). When you submit the form (request body), an inspector (Pydantic) checks everything before processing.

Example: Basic Pydantic Models

from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr
from typing import Optional, List
from datetime import datetime

app = FastAPI()

class UserCreate(BaseModel):
    name: str = Field(..., min_length=2, max_length=50)
    email: EmailStr
    age: int = Field(ge=0, le=150)
    password: str = Field(..., min_length=8)
    role: str = Field(default="user", pattern="^(user|admin)$")
    tags: List[str] = []

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    role: str
    created_at: datetime

    model_config = {"from_attributes": True}

@app.post("/api/users", response_model=UserResponse, status_code=201)
def create_user(user: UserCreate):
    """Create user with Pydantic validation."""
    # user is validated UserCreate instance
    # Return a dict that matches UserResponse
    return {
        "id": 1,
        "name": user.name,
        "email": user.email,
        "role": user.role,
        "created_at": datetime.now()
    }

Example: Nested Models and Validation

from pydantic import BaseModel, Field, field_validator
from typing import List, Optional

class Address(BaseModel):
    street: str = Field(..., min_length=5)
    city: str = Field(..., min_length=2)
    zip_code: str = Field(..., pattern="^[0-9]{5}(-[0-9]{4})?$")
    country: str = Field(default="USA")

class OrderItem(BaseModel):
    product_id: int = Field(..., ge=1)
    quantity: int = Field(..., ge=1, le=100)
    unit_price: float = Field(..., gt=0)

    @field_validator("unit_price")
    @classmethod
    def check_price(cls, v):
        if v > 100000:
            raise ValueError("Unit price cannot exceed 100000")
        return v

class OrderCreate(BaseModel):
    customer_id: int = Field(..., ge=1)
    items: List[OrderItem] = Field(..., min_length=1)
    shipping_address: Address
    notes: Optional[str] = Field(None, max_length=500)

@app.post("/api/orders", status_code=201)
def create_order(order: OrderCreate):
    total = sum(item.quantity * item.unit_price for item in order.items)
    return {
        "order_id": 123,
        "total": total,
        "items_count": len(order.items),
        "shipping": order.shipping_address.model_dump()
    }

Example: Multiple Body Parameters

from fastapi import FastAPI, Body
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

class User(BaseModel):
    username: str
    email: str

@app.post("/api/orders")
def create_order_with_user(
    item: Item,
    user: User,
    priority: int = Body(ge=1, le=5),
    coupon: str = Body(None, max_length=20)
):
    return {
        "item": item.model_dump(),
        "user": user.model_dump(),
        "priority": priority,
        "coupon": coupon
    }

Common Mistakes

  1. Not using Field validation — Without Field(min_length, max_length, pattern), you lose validation power. Always add constraints to fields.
  2. Missing model_config for ORM mode — When returning SQLAlchemy models, set model_config = {"from_attributes": True} to enable attribute access.
  3. Putting validation in the route handler — Define validation in Pydantic models using field_validator, not in the handler function.
  4. Not using EmailStr for emails — Import EmailStr from pydantic. It validates email format automatically. Using str misses email validation.
  5. Forgetting to use response_model — Without response_model, FastAPI returns all fields including sensitive ones. Define response models that exclude passwords.

Practice Questions

  1. What is the difference between Field and field_validator?
  2. How do you create nested Pydantic models?
  3. What does response_model do in a route decorator?
  4. How do you make a field optional with a default value?
  5. Challenge: Design Pydantic models for a hotel booking system. Include models for Guest, Booking (with check-in/out dates), Payment (amount, method, card_last_four), and Room (type, rate, amenities). Add appropriate validation for each field.

FAQ

What is the difference between Pydantic v1 and v2?

Pydantic v2 is 5-50x faster, uses Rust-based validation (pydantic-core), and has a different validator syntax. Use v2 with FastAPI 0.100+.

How do I create custom validators?

Use the @field_validator decorator on a classmethod. It receives the field value and should return the validated value or raise ValueError.

Can I reuse Pydantic models across endpoints?

Yes, define models in a schemas module and import them in multiple route files. Create base models and extend them with inheritance.

What is the difference between BaseModel and dataclass?

Pydantic BaseModel provides validation, serialization, JSON schema generation, and ORM support. Dataclasses are lighter but lack validation.

How do I handle optional fields in request bodies?

Use Optional[type] = None or type | None = None. FastAPI treats these as optional fields in the JSON body.

Mini Project

Design a complete set of Pydantic models for a social media API. Include models for: User (register, profile, public), Post (create, response, with author details), Comment (create, response with nesting), and Feed (paginated response with posts). Add validation for emails, URLs, text lengths, and custom validators for password strength and username format.

What's Next

Now learn about response models in Building REST APIs with FastAPI.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro