Backend Development 8 min read

Master FastAPI Request Body Validation with Pydantic Models

This guide explains how FastAPI uses Pydantic models to define, validate, and parse JSON and form request bodies, set default values and custom rules, handle nested structures, and return response models, providing a type‑safe, efficient way to build backend APIs.

Code Mala Tang
Code Mala Tang
Code Mala Tang
Master FastAPI Request Body Validation with Pydantic Models

When a client sends a POST, PUT, or PATCH request, the data in the request body can be defined using Pydantic models in FastAPI.

Processing JSON request bodies

FastAPI automatically converts incoming JSON payloads into Python objects based on the Pydantic model.

<code>from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# Define data model
class User(BaseModel):
    username: str
    email: str
    age: int

@app.post("/users/")
def create_user(user: User):
    return {"message": "User created successfully", "user": user}
</code>

The User model specifies the expected fields (username, email, age). The create_user endpoint validates the JSON and returns the parsed User object.

Example JSON request

<code>{
    "username": "john_doe",
    "email": "[email protected]",
    "age": 25
}
</code>

Example response:

<code>{
    "message": "User created successfully",
    "user": {
        "username": "john_doe",
        "email": "[email protected]",
        "age": 25
    }
}
</code>

Pydantic internally uses BaseModel.parse_obj() to:

Check field matching (missing or extra fields)

Perform type conversion (e.g., string to int)

Apply validation rules (regex, range, etc.)

Instantiate the model (User object)

It leverages Python type hints to automatically parse int, float, str, bool, datetime, UUID, Decimal, and recursively handle nested models or lists.

Setting default values and validation rules

Pydantic offers rich field validation, allowing defaults, constraints, and regex patterns.

<code>from pydantic import BaseModel, Field, EmailStr

class User(BaseModel):
    username: str = Field(..., min_length=3, max_length=20)
    email: EmailStr
    age: int = Field(..., gt=0, lt=120)  # age must be >0 and <120
</code>

Field(..., min_length=3) makes the field required with a minimum length of 3.

EmailStr validates email format automatically.

gt and lt set numeric ranges.

Custom validators can be added:

<code>from pydantic import validator

class User(BaseModel):
    username: str
    age: int

    @validator('username')
    def name_must_not_be_empty(cls, v):
        if not v.strip():
            raise ValueError('username cannot be empty')
        return v
</code>

If validation fails, FastAPI returns a 422 response with detailed error information:

<code>{
    "detail": [
        {
            "loc": ["body", "age"],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}
</code>

Validation logic can be reused with dependency injection:

<code>from fastapi import Depends, HTTPException

def verify_age(user: User):
    if user.age < 18:
        raise HTTPException(status_code=400, detail="User must be 18 or older")
    return user

@app.post("/register/")
def register_user(user: User = Depends(verify_age)):
    return {"message": "User registered", "user": user}
</code>

Handling form data with Form()

To receive form fields instead of JSON, install python-multipart and use Form :

<code>pip install python-multipart
</code>
<code>from fastapi import Form, FastAPI
from typing import Annotated

app = FastAPI()

@app.post("/login/")
def login(
    username: Annotated[str, Form()],
    password: Annotated[str, Form()]
):
    return {"username": username, "password": password}
</code>

Example curl request:

<code>curl -X POST "http://127.0.0.1:8000/login" -d "username=john_doe&password=secret"
</code>

Response:

<code>{
    "username": "john_doe",
    "password": "secret"
}
</code>

Nested models

Complex structures can be built by nesting Pydantic models:

<code>class Address(BaseModel):
    city: str
    zip_code: str

class User(BaseModel):
    username: str
    email: str
    age: int
    address: Address
</code>
<code>{
    "username": "john_doe",
    "email": "[email protected]",
    "age": 25,
    "address": {
        "city": "New York",
        "zip_code": "10001"
    }
}
</code>

Response models

Response models hide internal fields and enforce a consistent output format:

<code>class UserResponse(BaseModel):
    username: str
    email: str

@app.post("/users/", response_model=UserResponse)
def create_user(user: User):
    return user
</code>

If the User model contains a password field, it will not be returned because the response model only includes the defined fields.

Summary

FastAPI leverages Pydantic’s powerful validation to simplify request‑body handling for JSON, form data, and file uploads, enabling developers to build reliable, type‑safe APIs quickly.

Further reading:

FastAPI official documentation: https://fastapi.tiangolo.com/

Pydantic documentation: https://docs.pydantic.dev/

Pythonbackend developmentAPI designfastapiRequest ValidationPydantic
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.