Skip to content

Talking Back: Response Models and Error Handling

Talking Back: Response Models and Error Handling

Section titled “Talking Back: Response Models and Error Handling”

An API is a two-way conversation. We’ve mastered listening (inputs); now we must master speaking (responses). In this chapter, we will learn how to strictly define what our API sends back to the client and how to communicate errors gracefully.

By default, FastAPI allows you to return nearly anything—dictionaries, lists, or database objects—and it will convert them to JSON. However, explicitly defining a Response Model offers critical benefits:

  • Data Filtering: It restricts output data to exactly what is defined in the model.
  • Security: It prevents accidental leakage of sensitive fields like passwords.
  • Documentation: It generates JSON Schemas for the response in the API docs.

Let’s assume we have a User model for input that includes a password, but we want a separate model for output that excludes it.

from pydantic import BaseModel
class UserIn(BaseModel):
username: str
password: str
email: str
class UserOut(BaseModel):
username: str
email: str
# password is deliberately missing
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user

Even though we return the user object (which contains the password), FastAPI detects the response_model=UserOut and automatically filters the data. The client receives only the username and email.

In HTTP, the status code tells the client the result of the operation. While 200 OK is the default, it’s often more appropriate to use specific codes. For example, when creating a new resource, 201 Created is the standard.

FastAPI provides a shortcut via the status module to avoid memorizing numbers.

from fastapi import status
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
return {"name": name}

Sometimes, things go wrong. A user requests an ID that doesn’t exist, or they lack permission. You shouldn’t just return a dictionary saying {"error": "not found"} because that would still result in a 200 OK status code, confusing frontend logic.

Instead, use HTTPException.

from fastapi import HTTPException
items = {"foo": "The Foo Wrestler"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}

When you raise an HTTPException, FastAPI stops execution of the path operation immediately and sends a proper JSON error response to the client:

{
"detail": "Item not found"
}

You can also add custom headers to the error response if needed, making your error handling robust and compliant with HTTP standards.