FastAPI Custom Responses
Introduction
When building APIs with FastAPI, you'll often need more control over how your responses are structured and delivered. FastAPI provides powerful mechanisms to customize responses, allowing you to specify status codes, headers, content types, and more.
In this tutorial, we'll explore how to create and use custom responses in FastAPI. You'll learn how to customize every aspect of your API responses to meet specific requirements and improve the developer experience for API consumers.
Response Basics
By default, FastAPI automatically converts your return values to JSON and sets appropriate content types. However, many scenarios require custom response configurations:
- Returning specific HTTP status codes
- Setting custom headers
- Changing the response content type (XML, HTML, plain text)
- Streaming responses
- Returning files or binary data
- Creating custom response templates
FastAPI provides several ways to customize responses, from simple decorators to more complex response classes.
Using Status Codes
One of the simplest customizations is setting specific HTTP status codes.
Method 1: Using the status_code
parameter
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
return {"name": name, "created": True}
This endpoint will always return a 201 Created
status code when successful.
Method 2: Using the Response
parameter
from fastapi import FastAPI, Response, status
app = FastAPI()
@app.get("/items/{item_id}")
async def get_item(item_id: str, response: Response):
if item_id == "not_found":
response.status_code = status.HTTP_404_NOT_FOUND
return {"message": "Item not found"}
return {"item_id": item_id, "message": "Item found"}
With this approach, you can conditionally set status codes based on your business logic.
Custom Headers
Adding custom headers to your responses gives you more control over how clients interact with your API.
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/items/")
async def read_items(response: Response):
response.headers["X-Custom-Header"] = "Custom Value"
response.headers["Cache-Control"] = "max-age=3600"
return {"items": ["Item 1", "Item 2"]}
This is useful for adding authentication tokens, cache instructions, or custom tracking headers.
Response Models
FastAPI's response models allow you to define the exact structure of your responses:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: bool = None
@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: int):
# Database lookup logic here
return {
"name": "Example Item",
"price": 45.5,
"is_offer": True,
"secret_data": "This will be filtered out" # This won't appear in response
}
Response models offer several benefits:
- Data filtering (remove unnecessary fields)
- Data validation
- Automatic response documentation
- Type annotations for better IDE support
Returning Different Content Types
JSON Responses (Default)
FastAPI returns JSON responses by default.
from fastapi import FastAPI
app = FastAPI()
@app.get("/json/")
async def get_json():
return {"message": "This is JSON"}
HTML Responses
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/html/", response_class=HTMLResponse)
async def get_html():
return """
<html>
<head>
<title>FastAPI HTML Response</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
"""
Plain Text Responses
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.get("/text/", response_class=PlainTextResponse)
async def get_text():
return "Hello World!"
XML Responses
from fastapi import FastAPI
from fastapi.responses import Response
app = FastAPI()
@app.get("/xml/", response_class=Response)
async def get_xml():
content = """<?xml version="1.0" encoding="UTF-8"?>
<user>
<name>John Doe</name>
<email>[email protected]</email>
</user>
"""
return Response(content=content, media_type="application/xml")
File Responses
Returning files is common in many APIs:
from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()
@app.get("/file/")
async def get_file():
return FileResponse(
path="./files/report.pdf",
filename="user_report.pdf",
media_type="application/pdf"
)
Streaming Responses
For large files or real-time data, you can use streaming responses:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import io
app = FastAPI()
async def generate_csv():
# Create a file-like object in memory
buffer = io.StringIO()
buffer.write("id,name,email\n")
buffer.write("1,John Doe,[email protected]\n")
buffer.write("2,Jane Smith,[email protected]\n")
buffer.seek(0) # Reset the buffer position to the beginning
return buffer
@app.get("/download-csv/")
async def download_csv():
csv_file = await generate_csv()
# Return a streaming response
response = StreamingResponse(
iter([csv_file.getvalue()]),
media_type="text/csv"
)
response.headers["Content-Disposition"] = "attachment; filename=users.csv"
return response
Custom Response Classes
For advanced cases, you can create your own response class:
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
class CustomJSONResponse(JSONResponse):
def render(self, content):
# Add a timestamp to all responses
if isinstance(content, dict):
content["timestamp"] = "2023-05-15T14:30:00Z"
return super().render(content)
@app.get("/custom/", response_class=CustomJSONResponse)
async def get_with_custom():
return {"message": "Hello World"}
Output:
{
"message": "Hello World",
"timestamp": "2023-05-15T14:30:00Z"
}
Real-World Example: API Response Wrapper
Many APIs use a standard response format. Here's how to implement a consistent response structure:
from fastapi import FastAPI, Response, Request, status
from fastapi.responses import JSONResponse
from typing import Any, Dict, Optional
app = FastAPI()
class APIResponse(JSONResponse):
def __init__(
self,
data: Any = None,
message: str = "Success",
status_code: int = status.HTTP_200_OK,
headers: Optional[Dict[str, str]] = None
):
content = {
"status": "success" if status_code < 400 else "error",
"message": message,
"data": data
}
super().__init__(content=content, status_code=status_code, headers=headers)
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# Simulate database lookup
if user_id == 404:
return APIResponse(
message="User not found",
status_code=status.HTTP_404_NOT_FOUND
)
user_data = {"id": user_id, "name": "John Doe", "email": "[email protected]"}
return APIResponse(data=user_data, message="User retrieved successfully")
When calling /users/123
, you'll get:
{
"status": "success",
"message": "User retrieved successfully",
"data": {
"id": 123,
"name": "John Doe",
"email": "[email protected]"
}
}
When calling /users/404
, you'll get:
{
"status": "error",
"message": "User not found",
"data": null
}
Background Tasks with Responses
Sometimes you need to perform actions after returning a response:
from fastapi import FastAPI, BackgroundTasks
from fastapi.responses import JSONResponse
app = FastAPI()
async def send_notification(email: str, message: str):
# This would normally connect to a notification service
print(f"Sending email to {email}: {message}")
@app.post("/orders/")
async def create_order(
order: dict,
background_tasks: BackgroundTasks
):
# Process the order
order_id = "12345"
# Schedule a background task to send confirmation email
background_tasks.add_task(
send_notification,
"[email protected]",
f"Your order {order_id} has been confirmed"
)
# Return immediate response while email sends in background
return JSONResponse(
status_code=201,
content={"order_id": order_id, "status": "confirmed"},
headers={"Location": f"/orders/{order_id}"}
)
Summary
Custom responses give you complete control over what your API returns:
- Use status codes to accurately reflect the outcome of operations
- Set custom headers for caching, authentication, or other metadata
- Return different content types (HTML, XML, JSON, CSV)
- Stream large files or real-time data efficiently
- Create consistent response structures with custom response classes
- Work with background tasks for better performance
By leveraging FastAPI's response capabilities, you can create more powerful, flexible, and developer-friendly APIs.
Additional Resources
Exercises
- Create an API endpoint that returns user data as JSON but adds a custom
X-RateLimit-Remaining
header. - Implement a custom response class that automatically adds HATEOAS links to your resources.
- Build an endpoint that returns different content types (HTML, JSON, XML) based on an
Accept
header. - Create a file download endpoint that streams a large file and sets appropriate headers.
- Implement an error handling system using custom responses for your entire API.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)