My App
Pydantic Models Validation

Pydantic Models & Validation — Clean, Reliable, and Secure Data Handling

Learn how to create and validate Pydantic models in FastAPI, handle HTTP exceptions, write custom validators, and return response models with clean real-world examples.

🧩 Pydantic Models & Validation

Master the art of data validation and clean APIs using Pydantic.


🧠 What is Pydantic?

Pydantic is a Python library that ensures your data is correct and consistent.
It’s the backbone of FastAPI’s request and response validation.

Instead of manually checking every field, you define a model — and Pydantic validates it automatically.

💡 Think of Pydantic like a “gatekeeper”
it checks if the data coming into your app is correct before your logic even runs.


⚙️ 1. Pydantic Model Creation

🔹 Basic Concept

A Pydantic model is a Python class that defines the shape and type of your data.
When data doesn’t match, Pydantic raises an error automatically.


🔸 Example: Creating a Model

from pydantic import BaseModel

class Task(BaseModel):
    id: int
    title: str
    completed: bool = False

✅ This model ensures:

  • id is always an integer
  • title is a string
  • completed defaults to False if not provided

🔹 Using It in FastAPI

from fastapi import FastAPI

app = FastAPI()

@app.post("/tasks")
def create_task(task: Task):
    return {"message": "Task added", "data": task}

When a user sends invalid data (like a string instead of a number), Pydantic automatically responds with a 400 Bad Request and a clear error message.


💡 Real-World Example: In a Task Tracker app, this ensures no invalid task (like missing title or wrong data type) ever reaches your database.


🔒 2. Request Validation Patterns

🔹 Why Validation Matters

APIs often receive incorrect or incomplete data — validation ensures your backend is safe. Pydantic handles:

  • Missing fields
  • Wrong data types
  • Value constraints (like min length or valid email)

🔸 Example: Validate User Registration

from pydantic import BaseModel, EmailStr, Field

class User(BaseModel):
    name: str = Field(..., min_length=3)
    email: EmailStr
    password: str = Field(..., min_length=6, max_length=20)

Here:

  • Field(...) means the field is required
  • min_length and max_length ensure strong password rules
  • EmailStr automatically checks for valid email format

💡 Real-World Example: In a signup form, if a user enters an invalid email like "hello@com", FastAPI instantly rejects the request with a detailed error — no manual code needed.


🔹 Example of Error Response

If the request body is wrong:

{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

This structured error makes debugging and frontend validation easier.


🚨 3. HTTPException Handling

🔹 Why It’s Needed

Sometimes validation passes, but the logic still fails — for example, when a user tries to log in with a wrong password or duplicate email.

In such cases, you raise an HTTPException to return a proper HTTP response code.


🔸 Example

from fastapi import HTTPException

@app.post("/login")
def login(user: User):
    if user.email != "admin@example.com":
        raise HTTPException(status_code=404, detail="User not found")
    return {"message": "Login successful"}

💡 Real-World Example: When logging in to Twitter, if your email doesn’t exist, it returns “Invalid credentials” instead of a generic crash — same idea here.


🔹 Common Exception Codes

CodeMeaningWhen to Use
400Bad RequestInvalid input format
401UnauthorizedInvalid token or credentials
403ForbiddenUser doesn’t have permission
404Not FoundData not found
409ConflictDuplicate entry
500Server ErrorUnexpected issue

🧩 4. Custom Validators

🔹 Why Use Custom Validators?

Sometimes you need advanced validation logic that’s not covered by simple data types. Example: checking if a password contains both letters and numbers.


🔸 Example

from pydantic import validator

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

    @validator("password")
    def validate_password(cls, value):
        if len(value) < 6:
            raise ValueError("Password must be at least 6 characters")
        if not any(char.isdigit() for char in value):
            raise ValueError("Password must contain a number")
        return value

✅ Automatically ensures passwords are strong.


💡 Real-World Example: Just like how Gmail or Netflix prevents weak passwords, custom validators help enforce your own business rules.


🔹 Multiple Validators Example

@validator("username")
def username_no_spaces(cls, value):
    if " " in value:
        raise ValueError("Username cannot contain spaces")
    return value

💡 Pro Tip: Validators can even check relationships between multiple fields — for example, “end_date must be after start_date”.


🧾 5. Response Models

🔹 Why Use Response Models

When returning data to users, you often don’t want to expose sensitive fields (like passwords). Pydantic’s response models help define what data is safe to send back.


🔸 Example

from pydantic import BaseModel

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@app.post("/users", response_model=UserResponse)
def create_user(user: User):
    new_user = {"id": 1, "name": user.name, "email": user.email}
    return new_user

✅ Only id, name, and email are sent — password is automatically hidden.


💡 Real-World Example: When you sign up on LinkedIn, your password is stored securely, but only your name, email, and profile info are sent back to the frontend.


🔹 Response Models with Nested Data

You can also return relationships using nested models.

class Task(BaseModel):
    id: int
    title: str

class UserTasks(BaseModel):
    name: str
    tasks: list[Task]

✅ Clean, readable, and structured API responses.


🔍 6. Common Patterns for Clean Validation

PatternDescriptionExample
Model CompositionReuse models across routesBaseUser, UserCreate, UserResponse
Field ValidationUse Field() for constraintsage: int = Field(..., ge=18)
Custom ErrorsUse HTTPException for logic failuresraise HTTPException(404, "Not found")
Response FilteringUse response_model to hide fieldsHides passwords automatically

🌍 Real-World Integration Flow

Here’s how a FastAPI app uses Pydantic end-to-end:

  1. Request Body Validation — Incoming data is checked against the model.
  2. Logic Execution — Valid data is used safely.
  3. Exception Handling — Errors are raised cleanly using HTTPException.
  4. Response Modeling — Outgoing data is shaped using response models.

💡 Example Use Case: In a Todo App:

  • User sends a POST request with a task title
  • Pydantic checks it
  • FastAPI saves it
  • The API returns a clean, filtered response

🧾 Summary

ConceptDescriptionReal Example
Pydantic ModelsDefine structured dataTask model
Request ValidationCheck incoming dataSignup form validation
HTTPExceptionHandle logic errorsInvalid credentials
Custom ValidatorsEnforce custom rulesStrong passwords
Response ModelsControl output fieldsHide passwords in response

💡 Final Thought

Pydantic is the foundation of FastAPI’s reliability. It transforms messy user input into clean, type-safe data — and prevents countless bugs before they start.

💬 “Good APIs aren’t just fast — they’re reliable and predictable.”

By mastering Pydantic, you ensure both.