Skip to content

Response Models and Data Validation


🎯 What You’ll Learn

  • How to define response schemas using Pydantic
  • How FastAPI validates and filters response data
  • How to use response models to enforce structure and hide sensitive fields
  • How this improves documentation and client integration

📦 What Is a Response Model?

A response model is a Pydantic class that defines the structure of the data your API returns to clients. FastAPI uses it to:

  • Validate your return values
  • Filter out unexpected or sensitive fields
  • Automatically generate response schemas in the docs

This is especially useful when:

  • You want to hide internal fields (e.g. passwords, database IDs)
  • You want to guarantee consistent output for frontend or API consumers

🛠️ Step-by-Step: Defining a Response Model

Let’s say we have a Task model with extra internal fields we don’t want to expose.

📄 main.py

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# Internal model (e.g. from database)
class Task(BaseModel):
    id: int
    title: str
    completed: bool
    internal_notes: str

# Public response model
class TaskResponse(BaseModel):
    title: str
    completed: bool

@app.get("/task", response_model=TaskResponse)
def get_task():
    task = Task(
        id=1,
        title="Buy milk",
        completed=False,
        internal_notes="Urgent task"
    )
    return task

🔍 What’s Happening Here?

  • Task is your full internal model.
  • TaskResponse is the public-facing version.
  • response_model=TaskResponse tells FastAPI to:
    • Validate the returned data
    • Filter out fields not in TaskResponse (like id and internal_notes)
    • Generate clean documentation

🧪 Try:

  • Visit /task → You’ll get:

    {
      "title": "Buy milk",
      "completed": false
    }
    
  • Visit /docs → You’ll see the response schema matches TaskResponse


🔐 Why This Matters

  • Security: Prevent leaking sensitive fields (e.g. passwords, tokens)
  • Consistency: Frontend always gets the same shape of data
  • Documentation: Clients know exactly what to expect

🧪 Example: Filtering Sensitive User Data

class User(BaseModel):
    id: int
    username: str
    email: str
    password: str  # sensitive

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

@app.get("/user", response_model=PublicUser)
def get_user():
    user = User(
        id=1,
        username="enrico",
        email="enrico@example.com",
        password="supersecret"
    )
    return user

🧭 Try:

  • /user → ✅ Only returns username and email
  • /docs → Shows only the public fields

🧠 Optional: Response Model Configuration

You can customize response behavior using Pydantic’s Config class:

class TaskResponse(BaseModel):
    title: str
    completed: bool

    class Config:
        orm_mode = True
  • orm_mode = True allows FastAPI to work with ORM objects (e.g. SQLAlchemy) that aren’t plain dicts.
  • This becomes essential when you connect to a database later.

🧠 Practice Challenge

Create a User model with:

  • id, username, email, password, is_admin

Then:

  • Create a PublicUser response model that excludes password and is_admin
  • Return a full User object from an endpoint, but use response_model=PublicUser
  • Test it in /docs and verify the output is filtered