Response Models
Control API output with response_model, status codes, and field exclusion.
Yesterday we used Pydantic for request validation. Can I also use it to control what the API sends back?
Absolutely. That's what response_model does. It tells FastAPI: "No matter what my function returns, filter and validate it through this model before sending it to the client."
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class TaskResponse(BaseModel):
id: int
title: str
status: str
@app.get("/tasks/{task_id}", response_model=TaskResponse)
def get_task(task_id: int):
# Imagine this comes from a database
return {
"id": task_id,
"title": "Buy milk",
"status": "pending",
"internal_notes": "secret stuff" # This gets stripped!
}
The response only contains id, title, and status. The internal_notes field is filtered out because it's not in TaskResponse. This is how you prevent leaking internal data.
So I can have a different model for input and output?
That's the standard pattern. Separate models for different purposes:
class TaskCreate(BaseModel):
title: str
description: str = ""
priority: int = 1
class TaskResponse(BaseModel):
id: int
title: str
description: str
priority: int
status: str
@app.post("/tasks", response_model=TaskResponse, status_code=201)
def create_task(task: TaskCreate):
return {
"id": 42,
**task.model_dump(),
"status": "pending"
}
Notice status_code=201 — that sets the HTTP status code to 201 Created instead of the default 200 OK. Use 201 for creation, 204 for deletion, 200 for reads and updates.
What if some fields should only appear sometimes? Like, hide the email when listing users but show it on the detail view?
Use different response models for different endpoints:
class UserSummary(BaseModel):
id: int
name: str
class UserDetail(BaseModel):
id: int
name: str
email: str
created_at: str
@app.get("/users", response_model=list[UserSummary])
def list_users():
return [{"id": 1, "name": "Alice", "email": "a@b.com", "created_at": "2024-01-01"}]
@app.get("/users/{user_id}", response_model=UserDetail)
def get_user(user_id: int):
return {"id": user_id, "name": "Alice", "email": "a@b.com", "created_at": "2024-01-01"}
The list endpoint strips email and created_at. The detail endpoint shows everything. Same data, different views.
Can the response_model be a list?
Yes — response_model=list[TaskResponse] validates every item in the returned list. FastAPI handles both single objects and collections cleanly.
Practice your skills
Sign up to write and run code in this lesson.
Response Models
Control API output with response_model, status codes, and field exclusion.
Yesterday we used Pydantic for request validation. Can I also use it to control what the API sends back?
Absolutely. That's what response_model does. It tells FastAPI: "No matter what my function returns, filter and validate it through this model before sending it to the client."
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class TaskResponse(BaseModel):
id: int
title: str
status: str
@app.get("/tasks/{task_id}", response_model=TaskResponse)
def get_task(task_id: int):
# Imagine this comes from a database
return {
"id": task_id,
"title": "Buy milk",
"status": "pending",
"internal_notes": "secret stuff" # This gets stripped!
}
The response only contains id, title, and status. The internal_notes field is filtered out because it's not in TaskResponse. This is how you prevent leaking internal data.
So I can have a different model for input and output?
That's the standard pattern. Separate models for different purposes:
class TaskCreate(BaseModel):
title: str
description: str = ""
priority: int = 1
class TaskResponse(BaseModel):
id: int
title: str
description: str
priority: int
status: str
@app.post("/tasks", response_model=TaskResponse, status_code=201)
def create_task(task: TaskCreate):
return {
"id": 42,
**task.model_dump(),
"status": "pending"
}
Notice status_code=201 — that sets the HTTP status code to 201 Created instead of the default 200 OK. Use 201 for creation, 204 for deletion, 200 for reads and updates.
What if some fields should only appear sometimes? Like, hide the email when listing users but show it on the detail view?
Use different response models for different endpoints:
class UserSummary(BaseModel):
id: int
name: str
class UserDetail(BaseModel):
id: int
name: str
email: str
created_at: str
@app.get("/users", response_model=list[UserSummary])
def list_users():
return [{"id": 1, "name": "Alice", "email": "a@b.com", "created_at": "2024-01-01"}]
@app.get("/users/{user_id}", response_model=UserDetail)
def get_user(user_id: int):
return {"id": user_id, "name": "Alice", "email": "a@b.com", "created_at": "2024-01-01"}
The list endpoint strips email and created_at. The detail endpoint shows everything. Same data, different views.
Can the response_model be a list?
Yes — response_model=list[TaskResponse] validates every item in the returned list. FastAPI handles both single objects and collections cleanly.
Practice your skills
Sign up to write and run code in this lesson.