FastAPI Response Models and Status Codes
A response model controls exactly what your API sends back to the client. A status code tells the client whether the request succeeded, failed, or needs further action. Together they make your API predictable and easy to use.
Why Response Models Matter
Imagine your database stores user records with a password field. Without a response model, you might accidentally send the password to the client. A response model acts as a filter — it only passes through the fields you explicitly allow.
Database record:
{
"id": 1,
"name": "Meera",
"email": "m@example.com",
"password": "hashed_secret" ← never send this
}
Response model output (password removed):
{
"id": 1,
"name": "Meera",
"email": "m@example.com"
}
Defining a Response Model
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserIn(BaseModel): ← what client sends
name: str
email: str
password: str
class UserOut(BaseModel): ← what client receives
id: int
name: str
email: str
@app.post("/users", response_model=UserOut)
def create_user(user: UserIn):
# Save user to database (password included internally)
# Return object — FastAPI strips fields not in UserOut
return {"id": 42, "name": user.name, "email": user.email, "password": user.password}
Even though the returned dictionary contains password, FastAPI removes it because password is not in UserOut. The client never sees it.
Response Model as a Contract
Input model (UserIn) Output model (UserOut) ┌─────────────────┐ ┌──────────────────┐ │ name: str │ │ id: int │ │ email: str │ API │ name: str │ │ password: str │ ──────▶ │ email: str │ └─────────────────┘ └──────────────────┘ FastAPI enforces the output shape. No extra fields escape accidentally.
HTTP Status Codes
Every HTTP response carries a three-digit number that signals the result:
Range Meaning Examples
──────────────────────────────────────────────────
2xx Success 200 OK, 201 Created, 204 No Content
3xx Redirect 301 Moved, 302 Found
4xx Client error 400 Bad Request, 401 Unauthorized,
403 Forbidden, 404 Not Found,
422 Unprocessable Entity
5xx Server error 500 Internal Server Error
Setting Status Codes in FastAPI
@app.post("/items", status_code=201) ← 201 Created
def create_item():
...
@app.delete("/items/{id}", status_code=204) ← 204 No Content
def delete_item(id: int):
return None
Using status_code in the Swagger docs shows the expected response for that route automatically.
Using the status Module for Readable Code
Hard-coding numbers like 201 is error-prone. Use FastAPI's built-in status constants instead:
from fastapi import status
@app.post("/orders", status_code=status.HTTP_201_CREATED)
def place_order():
return {"order": "placed"}
@app.delete("/orders/{id}", status_code=status.HTTP_204_NO_CONTENT)
def cancel_order(id: int):
return None
Response Model with a List
Return a list of objects by wrapping the model in List:
from typing import List
@app.get("/users", response_model=List[UserOut])
def get_users():
return [
{"id": 1, "name": "Meera", "email": "m@example.com", "password": "x"},
{"id": 2, "name": "Raj", "email": "r@example.com", "password": "y"},
]
FastAPI filters password from every object in the list.
Excluding Unset Fields from the Response
Use response_model_exclude_unset=True to only return fields that were actually set — useful for PATCH endpoints:
@app.patch("/users/{id}", response_model=UserOut, response_model_exclude_unset=True)
def partial_update(id: int, user: UserOut):
...
Key Points
- The
response_modelparameter filters the output to only include specified fields. - Use separate input and output models to prevent sensitive data leaks.
- HTTP status codes signal success (2xx), client errors (4xx), or server errors (5xx).
- Use
fastapi.statusconstants for readable, self-documenting status codes. - Response models appear in your API docs, giving clients a clear contract.
