🎁 FastAPI Response Handling: The Gift-Wrapping Factory
Imagine you’re running a gift-wrapping factory. People send you items, and your job is to wrap them beautifully before sending them back. That’s exactly what Response Handling does in FastAPI!
Your API receives requests, does some work, and then wraps the answer in a nice package before sending it back. Let’s learn how to be the best gift-wrapper in town!
🎯 What You’ll Learn
| Topic | What It Does |
|---|---|
| Response Models | The “box” your gift goes in |
| Response Model Config | Rules for how to wrap |
| HTTP Status Codes | Labels saying “Success!” or “Oops!” |
| Custom Response Types | Different wrapping paper styles |
| Cookies & Headers | Little notes attached to the gift |
| JSON Compatible Encoder | Making sure everything fits in the box |
📦 Response Models: The Perfect Box
Think of a Response Model as the box you put your gift in. It decides:
- What can go inside
- What shape it should be
- What people will see when they open it
The Problem Without a Box
Without a response model, you might accidentally send too much:
# Oops! Sending everything,
# including the password!
@app.get("/user")
def get_user():
return {
"name": "Alice",
"email": "alice@mail.com",
"password": "secret123" # 😱
}
The Solution: Use a Response Model!
from pydantic import BaseModel
class UserOut(BaseModel):
name: str
email: str
# No password field = safe!
@app.get("/user", response_model=UserOut)
def get_user():
return {
"name": "Alice",
"email": "alice@mail.com",
"password": "secret123"
}
# Only name & email go out! ✅
Magic! FastAPI automatically removes the password because UserOut doesn’t include it.
graph TD A[Your Data] --> B{Response Model Filter} B --> C[Safe Data Sent] B --> D[Sensitive Data Blocked] D --> E[🚫 Never Leaves Server]
⚙️ Response Model Configuration: Custom Wrapping Rules
Sometimes you need special rules for wrapping. FastAPI gives you configuration options!
Exclude Unset Values
Only send values that were actually set:
@app.get(
"/item",
response_model=Item,
response_model_exclude_unset=True
)
def get_item():
return Item(name="Toy")
# Only sends: {"name": "Toy"}
# Skips fields with default values!
Include or Exclude Specific Fields
# Only send these fields
@app.get(
"/item",
response_model=Item,
response_model_include={"name", "price"}
)
# Send everything EXCEPT these
@app.get(
"/item",
response_model=Item,
response_model_exclude={"secret_code"}
)
All Config Options at a Glance
| Option | What It Does |
|---|---|
response_model_exclude_unset |
Skip fields user didn’t set |
response_model_exclude_defaults |
Skip fields with default values |
response_model_exclude_none |
Skip fields that are None |
response_model_include |
Only include these fields |
response_model_exclude |
Remove these fields |
🚦 HTTP Status Codes: The Label on Your Package
When you send a package, you put a label:
- ✅ “Delivered Successfully!”
- ❌ “Address Not Found”
- ⚠️ “Package Too Heavy”
HTTP Status Codes are those labels for your API!
Common Status Codes (Like Traffic Lights!)
| Code | Meaning | Like… |
|---|---|---|
| 200 | OK - All good! | 🟢 Green light |
| 201 | Created - New thing made! | 🎂 Birthday! |
| 204 | No Content - Done, nothing to say | 👍 Thumbs up |
| 400 | Bad Request - You asked wrong | 🤷 “Huh?” |
| 404 | Not Found - Doesn’t exist | 👻 Ghost |
| 500 | Server Error - We messed up | 💥 Boom |
Setting Status Codes
from fastapi import status
@app.post(
"/item",
status_code=status.HTTP_201_CREATED
)
def create_item(item: Item):
# Save item...
return item
# Returns with code 201!
Different Codes for Different Situations
from fastapi import HTTPException
@app.get("/item/{id}")
def get_item(id: int):
if id not in items:
raise HTTPException(
status_code=404,
detail="Item not found"
)
return items[id]
graph TD A[Request Arrives] --> B{Item Exists?} B -->|Yes| C[200 OK + Data] B -->|No| D[404 Not Found]
🎨 Custom Response Types: Different Wrapping Paper
Not everything needs to be wrapped in JSON! Sometimes you want:
- 📄 HTML pages
- 📁 Files to download
- 📝 Plain text
- 🔄 Streaming data
JSONResponse (Default Wrapping Paper)
from fastapi.responses import JSONResponse
@app.get("/data")
def get_data():
return JSONResponse(
content={"message": "Hello!"},
status_code=200
)
HTMLResponse (For Web Pages)
from fastapi.responses import HTMLResponse
@app.get("/page")
def get_page():
html = "<h1>Welcome!</h1>"
return HTMLResponse(content=html)
PlainTextResponse (Just Words)
from fastapi.responses import PlainTextResponse
@app.get("/hello")
def say_hello():
return PlainTextResponse("Hello there!")
FileResponse (Send a File)
from fastapi.responses import FileResponse
@app.get("/download")
def download_file():
return FileResponse(
path="report.pdf",
filename="my_report.pdf"
)
StreamingResponse (Big Data, Piece by Piece)
from fastapi.responses import StreamingResponse
def generate_data():
for i in range(10):
yield f"Chunk {i}\n"
@app.get("/stream")
def stream_data():
return StreamingResponse(
generate_data(),
media_type="text/plain"
)
Quick Reference: Response Types
| Type | Best For |
|---|---|
JSONResponse |
API data (default) |
HTMLResponse |
Web pages |
PlainTextResponse |
Simple text |
FileResponse |
Downloadable files |
StreamingResponse |
Large/continuous data |
RedirectResponse |
Send user elsewhere |
🍪 Response Cookies & Headers: Little Notes on the Gift
What Are Cookies?
Cookies are tiny notes your server leaves with the user’s browser. Like leaving a sticky note saying “Remember, this person likes chocolate!”
from fastapi import Response
@app.get("/login")
def login(response: Response):
response.set_cookie(
key="user_id",
value="abc123",
max_age=3600, # 1 hour
httponly=True # JS can't read it
)
return {"message": "Logged in!"}
Cookie Options Explained
| Option | What It Does |
|---|---|
key |
Name of the cookie |
value |
What to remember |
max_age |
How long to keep it (seconds) |
httponly |
Hide from JavaScript (safer!) |
secure |
Only send over HTTPS |
samesite |
Prevent cross-site attacks |
What Are Headers?
Headers are metadata about your response. Like the label on a shipping package showing weight, origin, and handling instructions.
@app.get("/data")
def get_data(response: Response):
response.headers["X-Custom-Info"] = "Hello!"
response.headers["Cache-Control"] = "max-age=60"
return {"data": "Here it is!"}
Common Headers
| Header | Purpose |
|---|---|
Content-Type |
What kind of data (json, html) |
Cache-Control |
How long to cache |
X-Request-ID |
Track this specific request |
Access-Control-* |
CORS permissions |
Deleting Cookies
@app.get("/logout")
def logout(response: Response):
response.delete_cookie("user_id")
return {"message": "Logged out!"}
graph TD A[Server Response] --> B[Response Body] A --> C[Headers] A --> D[Cookies] C --> E[Metadata about response] D --> F[Data stored in browser]
🔄 JSON Compatible Encoder: Making Everything Fit
Sometimes your data doesn’t fit nicely into JSON. Imagine trying to put a round peg in a square hole.
The Problem
from datetime import datetime
# This WILL cause problems:
data = {
"name": "Event",
"date": datetime.now() # 😱 JSON doesn't know dates!
}
The Solution: jsonable_encoder
from fastapi.encoders import jsonable_encoder
from datetime import datetime
event = {
"name": "Party",
"date": datetime.now()
}
safe_data = jsonable_encoder(event)
# Now date is a string: "2024-01-15T10:30:00"
What It Converts
| Python Type | Becomes JSON |
|---|---|
datetime |
ISO string |
UUID |
String |
Pydantic Model |
Dictionary |
Decimal |
Float |
bytes |
String (base64) |
set |
List |
Real Example: Saving to Database
from fastapi.encoders import jsonable_encoder
class Item(BaseModel):
name: str
price: float
created: datetime
@app.post("/item")
def create_item(item: Item):
# Convert to JSON-safe dict
item_data = jsonable_encoder(item)
# Now safe to save to database
# or send anywhere!
db.save(item_data)
return item_data
When to Use It
- ✅ Before saving to a database
- ✅ Before caching data
- ✅ When returning non-standard types
- ✅ When building custom responses
🎯 Putting It All Together
Here’s a complete example using everything we learned:
from fastapi import FastAPI, Response, status
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from datetime import datetime
app = FastAPI()
class ItemIn(BaseModel):
name: str
price: float
secret_code: str
class ItemOut(BaseModel):
name: str
price: float
created_at: datetime
@app.post(
"/items",
response_model=ItemOut,
status_code=status.HTTP_201_CREATED,
response_model_exclude_none=True
)
def create_item(
item: ItemIn,
response: Response
):
# Create new item with timestamp
new_item = {
**item.dict(),
"created_at": datetime.now()
}
# Make it JSON-safe
safe_item = jsonable_encoder(new_item)
# Add custom header
response.headers["X-Item-Created"] = "true"
# Set a cookie
response.set_cookie(
key="last_item",
value=new_item["name"]
)
return safe_item
🌟 Key Takeaways
- Response Models = Your safety filter (like a bouncer)
- Config Options = Fine-tune what gets sent
- Status Codes = Tell users what happened (success/error)
- Custom Responses = Different formats for different needs
- Cookies & Headers = Extra info attached to responses
- jsonable_encoder = Convert weird Python types to JSON
🎁 Remember the Gift-Wrapping Analogy!
| Concept | Gift-Wrapping Analogy |
|---|---|
| Response Model | The box shape |
| Config | Wrapping rules |
| Status Code | The label (“Fragile!”, “Priority!”) |
| Response Type | The wrapping paper style |
| Cookies | Sticky notes for later |
| Headers | Shipping label info |
| JSON Encoder | Making odd items fit the box |
You’re now a Response Handling expert! 🎉
Go wrap some beautiful API responses!