Responses & Status Codes
Returning JSON
Section titled “Returning JSON”In Express you call res.json(data). In FastAPI you just return a Python dict, list, or Pydantic model from the handler function — FastAPI serializes it to JSON automatically.
// Expressapp.get('/items/:id', (req, res) => { res.json({ id: Number(req.params.id), name: 'Widget' });});
// With status codeapp.post('/items', (req, res) => { res.status(201).json({ id: 42, ...req.body });});# FastAPIfrom fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")async def get_item(item_id: int): return {"id": item_id, "name": "Widget"} # auto-serialized
@app.post("/items", status_code=201) # default status via decoratorasync def create_item(item: dict): return {"id": 42, **item}response_model — filtering and shaping the output
Section titled “response_model — filtering and shaping the output”response_model is a decorator argument that tells FastAPI which Pydantic model to use when serializing the response. It filters out any fields not in the model (useful for stripping passwords or internal fields) and validates outgoing data.
// TypeScript — you manually pick fieldsinterface ItemPublic { id: number; name: string; price: number; }
app.get('/items/:id', (req, res) => { const dbRow = getFromDB(req.params.id); const { id, name, price } = dbRow; // manually strip secret fields res.json({ id, name, price } as ItemPublic);});# FastAPI response_model strips fields automaticallyfrom fastapi import FastAPIfrom pydantic import BaseModel
class ItemInDB(BaseModel): # full model with internal fields id: int name: str price: float hashed_password: str # should NOT be returned
class ItemPublic(BaseModel): # public-safe model id: int name: str price: float
app = FastAPI()
@app.get("/items/{item_id}", response_model=ItemPublic)async def get_item(item_id: int): # hashed_password is stripped automatically return ItemInDB(id=item_id, name="Widget", price=9.99, hashed_password="secret")Status codes
Section titled “Status codes”FastAPI exposes the status module from Starlette for named constants — the equivalent of Node’s http-status package.
// Express with http-statusimport status from 'http-status';res.status(status.CREATED).json(data); // 201res.status(status.NO_CONTENT).send(); // 204res.status(status.NOT_FOUND).json({...}); // 404# FastAPI status constantsfrom fastapi import FastAPIfrom fastapi import status
app = FastAPI()
@app.post("/items", status_code=status.HTTP_201_CREATED)async def create_item(item: dict): return item
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)async def delete_item(item_id: int): return None # 204 — no bodyJSONResponse — explicit control
Section titled “JSONResponse — explicit control”When you need to set custom headers or override the status code dynamically, use JSONResponse directly — the equivalent of constructing a res object manually in Express.
// Express — set headers manuallyapp.get('/download', (req, res) => { res.set('X-Custom-Header', 'value'); res.status(200).json({ url: 'https://...' });});# FastAPI JSONResponsefrom fastapi import FastAPIfrom fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/download")async def download(): return JSONResponse( content={"url": "https://example.com/file.zip"}, status_code=200, headers={"X-Custom-Header": "value"}, )Try it — JSON serialization with stdlib
Section titled “Try it — JSON serialization with stdlib”Run this locally for a real FastAPI response. The snippet below shows Python’s built-in
jsonmodule — the same serialization FastAPI uses under the hood.
import jsonfrom datetime import datetime
# Simulate a Pydantic model's .model_dump() outputitem = { "id": 1, "name": "Widget", "price": 9.99, "in_stock": True, "tags": ["sale", "new"], "created_at": datetime.utcnow().isoformat() + "Z",}
# Serialize — what FastAPI sends over the wireencoded = json.dumps(item, indent=2)print("Response body:")print(encoded)
# Deserialize — what the client receivesdecoded = json.loads(encoded)print(f"\nDecoded type: {type(decoded)}")print(f"name={decoded['name']}, price={decoded['price']}")
# response_model filtering equivalentpublic_fields = {"id", "name", "price"}filtered = {k: v for k, v in decoded.items() if k in public_fields}print(f"\nFiltered (response_model): {filtered}")Loading Python runtime (first run only)…