Pydantic V2 in FastAPI (Validation & Serialization)

05 Jul 2026, Updated: 30 Jul 2026 4 min read
1
In this article, we will explore Pydantic V2 from first principles and understand how it fits into the FastAPI request lifecycle.
Pydantic is responsible for converting untrusted input into validated Python objects while ensuring that application data conforms to the expected schema.

The Problem Pydantic Solves

Imagine an API that creates a new user. Without validation, an endpoint might look like this:
@app.post("/users")
async def create_user(user: dict):
    return user
Suppose the client sends:
{
    "name": 100,
    "age": "twenty"
}
The endpoint receives the data exactly as provided.

Without validation, every endpoint must manually verify:
- Does the name exist?
- Is the name a string?
- Is age present?
- Is age an integer?
- Is age within an acceptable range?

Pydantic solves this problem by validating incoming data before endpoint execution.

Creating Your First Pydantic Model

A Pydantic model represents the structure of the data your API expects.
from pydantic import BaseModel

class UserRequest(BaseModel):
    name: str
    age: int
The endpoint now becomes:
@app.post("/users")
async def create_user(user: UserRequest):
    return user
If the client sends:
{
    "name": "John",
    "age": 30
}
FastAPI automatically creates a UserRequest object and passes it to the endpoint. The endpoint receives a fully validated Python object instead of raw JSON.
What Happens Internally?

When a request reaches FastAPI, Pydantic validation occurs before the endpoint executes.

If validation fails, endpoint execution never begins. Instead, FastAPI immediately returns a 422 Unprocessable Entity response.

Field Validation Using field_validator()

Pydantic V2 replaces the old @validator decorator with @field_validator. Suppose user names must contain at least three characters.
from pydantic import BaseModel
from pydantic import field_validator

class UserRequest(BaseModel):
    name: str
    age: int

    @field_validator("name")
    @classmethod
    def validate_name(cls, value):

        if len(value) < 3:
            raise ValueError(
                "Name must contain at least 3 characters"
            )
        return value
Whenever a request arrives, the validator executes automatically. If validation fails, FastAPI returns a validation error without executing the endpoint.

Model Validation Using model_validator()

Sometimes validation depends on multiple fields. Suppose minors cannot register with a company email address.
from pydantic import BaseModel
from pydantic import model_validator

class UserRequest(BaseModel):
    age: int
    email: str

    @model_validator(mode="after")
    def validate_user(self):
        if self.age < 18 and self.email.endswith("@company.com"):
            raise ValueError(
                "Minors cannot use company email addresses."
            )
        return self
Unlike field validators, model validators have access to the complete object. This makes them suitable for cross-field validation.

Field Constraints

Many common validation rules can be declared without writing custom validators.
from pydantic import BaseModel
from pydantic import Field

class UserRequest(BaseModel):
    name: str = Field(
        min_length=3,
        max_length=50
    )
    age: int = Field(
        ge=18,
        le=100
    )
Pydantic automatically validates these constraints. No additional code is required.

Response Models

Pydantic is also used when returning responses.
class UserResponse(BaseModel):
    id: int
    name: str

@app.get(
    "/users/{id}",
    response_model=UserResponse
)
async def get_user(id: int):
    return {
        "id": id,
        "name": "John",
        "password": "secret"
    }
Although the endpoint returns a password field, FastAPI excludes it from the response because it is not defined in the response model. This prevents accidental exposure of sensitive data.
Using response models is one of the simplest ways to protect sensitive information in production APIs.

Serializing Models

Pydantic V2 replaces dict() with model_dump().
user = UserRequest(
    name="John",
    age=30
)

print(user.model_dump())
Output:
{
    "name": "John",
    "age": 30
}
Whenever data needs to be converted back into a dictionary, model_dump() should be used.

Creating Models from Existing Data

Pydantic V2 also introduces model_validate().
data = {
    "name": "John",
    "age": 30
}

user = UserRequest.model_validate(data)
Instead of manually constructing the object, Pydantic validates the input and creates the model. This method is commonly used when processing data from external systems.

Nested Models

Production APIs frequently contain nested objects.
class Address(BaseModel):
    city: str
    country: str

class UserRequest(BaseModel):
    name: str
    address: Address
Incoming JSON:
{
    "name": "John",
    "address": {
        "city": "London",
        "country": "UK"
    }
}
Pydantic recursively validates nested objects, ensuring every level of the request conforms to the expected schema.

Validation Errors

Suppose the client sends:
{
    "name": "Jo",
    "age": "abc"
}
Pydantic identifies all validation errors before the endpoint executes.
{
    "detail": [
        ...
    ]
}
The endpoint is never executed. This prevents invalid data from reaching business logic.
In production systems, Pydantic models should represent API contracts rather than database entities. Separate models are typically created for:
UserCreateRequest
UserUpdateRequest
UserResponse
UserSummary
UserDetails
Using dedicated request and response models makes APIs easier to evolve without affecting internal implementation.

Conclusion

Pydantic V2 is the foundation of FastAPI's request validation, response serialization, schema generation, and type safety.

By validating incoming data before endpoint execution, supporting custom validation rules, and generating well-defined API contracts, it helps developers build APIs that are reliable, maintainable, and secure.
Nagesh Chauhan

Nagesh Chauhan

Principal Software Engineer • Java • Python • Distributed Systems • AI/ML

Principal Software Engineer with 14+ years of experience designing and delivering large-scale distributed systems, cloud-native applications, and AI-powered platforms.

Passionate about solving complex engineering problems using strong data structures and algorithms, along with expertise in Java, Spring Boot, Python, System Design, Microservices, Cloud, Kafka, Elasticsearch, and Generative AI.

Share this Article

💬 Comments

Join the Discussion