Home/Learn/FastAPI/Response Models & Status Codes — Shaping What Goes Out

Response Models & Status Codes — Shaping What Goes Out

Intermediate
Fundamentals

response_model filters and documents what an endpoint returns — the classic win is UserOut without the password hash. status_code sets the right code per operation; HTTPException handles the sad paths.

Overview

Input validation is half the story; the response deserves the same rigour. response_model declares the output schema: FastAPI serializes through it, silently dropping any field not in the model — which is how you guarantee a password hash or internal flag never leaks, even if your function returns the full database object. Separate In/Out models (UserIn with password, UserOut without) are the standard pattern, with a shared base class to avoid repetition. status_code fixes the success code (201 for creation, 204 for deletion), and raising HTTPException produces clean error responses from anywhere in the call stack. Together these make the OpenAPI docs a true contract: exact shapes in, exact shapes out, per status code.

response_model — the Output Filter

Return anything; FastAPI reshapes it through response_model. Fields not in the model are removed from the response — leaking is structurally impossible, and the docs show the real output schema.

In-model with password, Out-model without — leak-proof by design
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserBase(BaseModel):
    name: str
    email: EmailStr

class UserIn(UserBase):               # what the client SENDS
    password: str

class UserOut(UserBase):              # what the client GETS — no password
    id: int

fake_db = {}

@app.post("/users", response_model=UserOut, status_code=201)
def register(user: UserIn):
    hashed = "bcrypt$" + user.password[::-1]        # (real hashing → Auth chapter)
    record = {"id": len(fake_db) + 1, **user.model_dump(), "password": hashed}
    fake_db[record["id"]] = record
    return record        # ← contains password! response_model strips it.

# POST /users {"name":"Asha","email":"asha@iitb.ac.in","password":"s3cret"}
# → 201 {"id": 1, "name": "Asha", "email": "asha@iitb.ac.in"}
#   password never leaves the server — enforced by the framework, not discipline

# Lists work too:
@app.get("/users", response_model=list[UserOut])
def all_users():
    return list(fake_db.values())

# Skip nulls in output:  response_model_exclude_none=True

Status Codes and HTTPException

Success codes are part of the contract: 201 Created, 204 No Content, 200 everything else. For failures, raise HTTPException — it short-circuits from any depth and renders {"detail": ...} with your code.

201/204 on success paths; raise HTTPException for sad paths
from fastapi import FastAPI, HTTPException, status

app = FastAPI()
orders = {1: {"item": "Kota doria saree", "amount": 1499}}

@app.get("/orders/{order_id}")
def get_order(order_id: int):
    order = orders.get(order_id)
    if not order:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,     # named constants read better
            detail=f"Order {order_id} not found",
        )
    return order
# GET /orders/99 → 404 {"detail": "Order 99 not found"}

@app.post("/orders", status_code=status.HTTP_201_CREATED)
def create_order(payload: dict):
    return payload                                     # → 201, not default 200

@app.delete("/orders/{order_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_order(order_id: int):
    orders.pop(order_id, None)
    return None                                        # 204 = empty body

# The cheat-sheet that interviewers expect:
#   200 OK          read/update success       400 bad request (semantic)
#   201 Created     POST success              401 not logged in
#   204 No Content  DELETE success            403 logged in, not allowed
#   422 validation  (FastAPI automatic)       404 not found  ·  409 conflict
#                                             500 bug — never raise on purpose

Key Points to Remember

  • 1response_model filters output — extra fields (password!) are dropped, guaranteed
  • 2Separate In/Out models sharing a base class is the standard pattern
  • 3Set status_code per operation: 201 create, 204 delete, 200 default
  • 4raise HTTPException(status_code, detail) from any depth for error responses

Interview Questions

Sign in to ask Aria
1

How do you guarantee a password hash can never appear in an API response?

MediumPhonePe
2

401 vs 403 vs 404 — give a concrete scenario for each in an orders API.

MediumZerodha
3

Why are separate UserIn and UserOut models better than one User model with optional fields?

HardMicrosoft

Ask Aria about Response Models & Status Codes — Shaping What Goes Out

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…