Models, Responses and Errors
Response models, status codes and raising HTTP errors.
What you will learn
- Declare response_model
- Set status codes
- Raise HTTPException
How an API replies matters as much as what it accepts: the right shape, the right status code and helpful errors. FastAPI makes each of these explicit.
Response models
Declaring a response_model filters the output to exactly the fields you specify. This is how you avoid accidentally leaking secrets, such as a password hash.
from pydantic import BaseModel
class UserIn(BaseModel):
username: str
password: str
class UserOut(BaseModel):
id: int
username: str
@app.post("/users", response_model=UserOut, status_code=201)
def create_user(user: UserIn):
saved = {"id": 1, "username": user.username, "password": "hashed..."}
return saved # password is removed by UserOutStatus codes
200 OK: success (default).201 Created: a resource was created (POST).204 No Content: success with nothing to return (DELETE).400 / 422: bad input.401 / 403: not authenticated / not allowed.404: not found.409: conflict.
from fastapi import HTTPException, status
users = {1: {"id": 1, "username": "ada"}}
@app.get("/users/{user_id}", response_model=UserOut)
def get_user(user_id: int):
if user_id not in users:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
return users[user_id]
@app.delete("/users/{user_id}", status_code=204)
def delete_user(user_id: int):
if users.pop(user_id, None) is None:
raise HTTPException(404, "User not found")Output
// GET /users/99
{"detail":"User not found"}from fastapi.responses import JSONResponse
class OutOfStock(Exception):
def __init__(self, item): self.item = item
@app.exception_handler(OutOfStock)
def out_of_stock_handler(request, exc: OutOfStock):
return JSONResponse(status_code=409, content={"error": f"{exc.item} is out of stock"})from typing import List
@app.get("/users", response_model=List[UserOut])
def list_users(skip: int = 0, limit: int = 20):
return list(users.values())[skip: skip + limit]Consistency
Pick one error shape for the whole API and stick to it; front-end code becomes much simpler when every error looks the same.
Try it yourself
Build a tiny in-memory /todos API: POST (201), GET by id (404 if missing) and DELETE (204).
Show solution
todos, next_id = {}, 1
class TodoIn(BaseModel):
title: str
@app.post("/todos", status_code=201)
def add(t: TodoIn):
global next_id
todos[next_id] = {"id": next_id, "title": t.title}
next_id += 1
return todos[next_id - 1]
@app.get("/todos/{tid}")
def get(tid: int):
if tid not in todos:
raise HTTPException(404, "Not found")
return todos[tid]
@app.delete("/todos/{tid}", status_code=204)
def remove(tid: int):
todos.pop(tid, None)