Search Topics

Search across all FastAPI topics

GitHub

Response Model

Core Concept

Response models control what data your API returns. They filter out sensitive fields, validate output, and generate accurate OpenAPI documentation.

Your /users/me endpoint returns the full user object — including the hashed_password field. A security audit flags it. You accidentally leaked password hashes to every frontend client.

terminal
GET /users/me → 200 OK

{
  "id": 1,
  "username": "alice",
  "email": "alice@example.com",
  "hashed_password": "$2b$12$LJ3m4ys3Lk.YHxQKfJk0R...",
  "is_admin": true,
  "created_at": "2024-01-15T10:30:00"
}

# hashed_password is visible to anyone!

Question

How does this happen? It's usually innocent — you return your database model directly, and it includes everything. The fix isn't to manually delete fields from a dict. It's to tell FastAPI what's allowed out. That's what response_model does.

How Response Model Filtering Works

Think of response_model as a security filter. Your function can return a full database object with 10 fields, but only the fields in the response model make it to the client. Everything else gets stripped.

DB object

10 fields (incl. password)

Your function

return user

response_model filter

Only 3 fields allowed

Client receives

3 safe fields

The filtering happens automatically. You don't need to manually build a safe dict — just declare the response model and FastAPI does the rest.

Response model concept

What you just learned

response_model acts as a whitelist — only declared fields reach the client

You can safely return full database objects without leaking sensitive data

The filtering is automatic — no manual dict construction needed

Fixing the Password Leak

Your endpoint returns the full user object including hashed_password. How do you create a safe response?

Broken code
main.py
from fastapi import FastAPI
from pydantic import BaseModel

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

app = FastAPI()

@app.post("/users")
async def create_user(user: User):
    # Returns EVERYTHING, including password!
    return user

Watch out

This is one of the most common security issues in APIs. It's not just passwords — think about internal IDs, admin flags, cost prices, or API keys. If it's in your database model, it can accidentally leak. Always use separate input and output models.

See the Filter in Action

Watch how response_model strips sensitive fields from your API response. Raw data goes in, only safe fields come out.

Input/output models

What you just learned

Use separate models for input (UserIn) and output (UserOut)

response_model=UserOut strips any fields not in UserOut

You can safely return full DB objects — FastAPI filters them for you

Go Deeper: Fine-Tuning the Response

Sometimes you need more control than just "include these fields." What if you want to exclude fields that weren't explicitly set? Or hide internal fields from a public endpoint?

main.py
@app.get(
    "/items/{item_id}",
    response_model=Item,
    response_model_exclude_unset=True,
)
async def read_item(item_id: int):
    # Only returns fields that were explicitly set
    # If description was never set, it won't appear (even as None)
    return items[item_id]

@app.get(
    "/items/{item_id}/public",
    response_model=Item,
    response_model_exclude={"internal_notes", "cost_price"},
)
async def read_item_public(item_id: int):
    # These two fields are always stripped, even if set
    return items[item_id]

Modern Alternative: Return Type Annotations

In newer FastAPI versions, you can skip response_model and use Python's return type annotation instead. Same behavior, cleaner syntax.

main.py
# Modern syntax — return type annotation
@app.get("/users/{user_id}")
async def read_user(user_id: int) -> UserOut:
    user = get_user(user_id)
    return user

# Equivalent to the explicit response_model:
# @app.get("/users/{user_id}", response_model=UserOut)

Insight

Both approaches do the same thing. The return type annotation is more Pythonic and gives you better IDE support. Use response_model when you need the extra options like response_model_exclude.

Response control

What you just learned

response_model_exclude_unset hides fields that were never set

response_model_exclude lets you blacklist specific fields

Return type annotations (-> UserOut) are the modern, cleaner alternative

Think about it...

If your response_model has 3 fields but your endpoint returns a dict with 10 fields, what happens to the extra 7?

Hint: Think of response_model as a filter, not a validator.

Key Points

Data Filtering

response_model automatically strips fields not in the model

Security

Prevent leaking passwords, internal IDs, and sensitive data

Return Type

Modern FastAPI supports -> ReturnType as response_model

Exclude Options

Fine-tune with exclude_unset, exclude_defaults, exclude