Pydantic Schema — Data Validation and Serialization

In this chapter, you will learn about Pydantic Schema and understand its separation of responsibilities from ORM models—this is FastAPI's most core feature.


Why do we need Schema?

ORM models (models.py) define the database structure, but cannot be directly used for API request/response validation.

Reason: ORM models contain database-specific logic (such as relationships), and you don't want to expose all fields to the API.

Pydantic SchemaIt is a pure data class, specifically responsible for API input/output validation and serialization.

The relationship among these three:

客户端 → [Pydantic Schema 校验] → 路由函数 → [ORM 模型] → 数据库
客户端 ← [Pydantic Schema 序列化] ← 路由函数 ← [ORM 模型] ← 数据库

Pydantic Schema Basics

Pydantic V2 is FastAPI's data engine, defining data structures through type hints.

Example

from pydantic import BaseModel, ConfigDict

class ItemCreate(BaseModel):
    """Schema for creating requests"""
    name: str
    price: float                       # Automatically validate type
    is_offer: bool = False             # With default values (optional fields)

class ItemResponse(BaseModel):
    """Response Schema"""
    id: int
    name: str
    price: float

    # V2 approach: enable ORM mode, allowing direct conversion from ORM objects.
    model_config = ConfigDict(from_attributes=True)

Define the Schema for the blog

Example

# File path: schemas.py
from pydantic import BaseModel, ConfigDict
from datetime import datetime
from typing import Optional

# ====== Category Schema ======
class CategoryBase(BaseModel):
    name: str
    slug: str

class CategoryResponse(CategoryBase):
    id: int
    model_config = ConfigDict(from_attributes=True)

# ====== Post Schema ======
class PostBase(BaseModel):
    title: str
    slug: str
    summary: Optional[str] = None      # Optional = can be None
    content: str
    category_id: int

class PostCreate(PostBase):
    """Request body Schema for creating an article."""
    pass                               # Directly inherits PostBase, fields are identical.

class PostUpdate(BaseModel):
    """Update article (all fields optional)"""
    title: Optional[str] = None
    slug: Optional[str] = None
    summary: Optional[str] = None
    content: Optional[str] = None
    category_id: Optional[int] = None

class PostResponse(PostBase):
    """Response Schema returned to the client."""
    id: int
    created_at: datetime
    updated_at: datetime
    category: CategoryResponse         # Nested Schema: Return category information

    model_config = ConfigDict(from_attributes=True)
    from_attributes=True tells Pydantic: this Schema can be created from ORM objects

In Pydantic V2,model_config = ConfigDict(from_attributes=True)Replaces the one in V1class Config: orm_mode = TrueThis configuration allows you to directly build a Pydantic Schema from SQLAlchemy ORM objects:PostResponse.model_validate(post_orm_obj)。


Using Schema in routes

Example

# File path: routers/posts.py (example of creating a POST route)
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db
from models import Post
from schemas import PostCreate, PostResponse

router = APIRouter(prefix="/posts", tags=["Article"])

# After declaring response_model=PostResponse:
# 1. FastAPI automatically converts ORM objects to Schema (serialization)
# 2. API documentation automatically displays the response structure
@router.post("/", response_model=PostResponse, status_code=201)
def create_post(post_data: PostCreate, db: Session = Depends(get_db)):
    """
Create new article
- post_data: request body, FastAPI automatically validates against the PostCreate Schema
db: database session injected via Depends(get_db)
    """

    post = Post(**post_data.model_dump())  # model_dump() converts Schema to dict
    db.add(post)
    db.commit()
    db.refresh(post)                       # Refresh and get the database auto-increment ID
    return post                            # FastAPI automatically serializes using PostResponse.

response_modelIt is one of FastAPI's strongest features. Once declared, FastAPI will: 1. Automatically filter out fields from ORM objects that are not declared in the Schema; 2. Automatically convert types such as datetime into JSON strings; 3. Generate precise OpenAPI documentation.


ORM Model vs Pydantic Schema Comparison

AspectSQLAlchemy ORM ModelPydantic Schema
Definition locationmodels.pyschemas.py
base classBase (DeclarativeBase)BaseModel (Pydantic)
ResponsibilitiesDefine database table structures and relationshipsDefine API input/output formats and validation rules.
Relationship with databaseDirectly maps to database tablesIt doesn't care about the database, only about the API contract.
Field declarationColumn(Integer, ...)Python type hints: int, str
serializationCannot be directly converted to JSONBuilt-in .model_dump() and JSON serialization
Conversion directionORM → Schema:model_validate(obj)Schema → ORM:Model(**schema.model_dump())

Chapter summary

In this chapter, you have mastered FastAPI's most core Pydantic Schema concepts: BaseModel for defining request/response Schemas, from_attributes=True to enable ORM mode, response_model for automatically serializing ORM objects, and the principle of separating responsibilities between Schemas and ORM models.

This is the essence of FastAPI's type-driven architecture—define the data shape first, and routes and documentation are generated automatically.

other extensions