FastAPI Response Objects
When building APIs with FastAPI, you need to control not just what data you send back but also how you send it. Response objects in FastAPI give you fine-grained control over HTTP responses, including status codes, headers, cookies, and more.
Introduction
In many API endpoints, returning content directly is sufficient:
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id, "name": "Example Item"}
FastAPI automatically converts the dictionary to JSON and sets the appropriate content type. But for more complex scenarios, you need Response objects.
Understanding Response Objects
Response objects allow you to:
- Set specific status codes
- Set custom headers
- Return specific content types
- Set cookies
- Send streaming responses
- Return files
FastAPI provides several response classes from Starlette, and adds some of its own.
Basic Response Types
JSONResponse
JSONResponse
is the default response used by FastAPI when you return a dict, list, or other JSON-serializable object.
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/custom-json/")
async def get_custom_json():
content = {"message": "This is a custom JSON response"}
return JSONResponse(content=content, status_code=200)
Output:
{
"message": "This is a custom JSON response"
}
PlainTextResponse
For returning plain text:
from fastapi.responses import PlainTextResponse
@app.get("/text/", response_class=PlainTextResponse)
async def get_text():
return "Hello World"
Output:
Hello World
HTMLResponse
For returning HTML content:
from fastapi.responses import HTMLResponse
@app.get("/html/", response_class=HTMLResponse)
async def get_html():
html_content = """
<html>
<head>
<title>FastAPI HTML Response</title>
</head>
<body>
<h1>Hello from FastAPI!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content)
Output would be a rendered HTML page with "Hello from FastAPI!" as an H1 heading.
Setting Status Codes
You can set status codes in two ways:
Using the status_code parameter:
@app.get("/items/", status_code=201)
async def create_item():
return {"message": "Item created"}
Using a Response object:
@app.get("/error/")
async def get_error():
content = {"error": "Something went wrong"}
return JSONResponse(content=content, status_code=400)
Working with Headers
Adding custom headers to your response:
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/headers/")
async def get_headers():
content = {"message": "Custom headers example"}
headers = {"X-Custom-Header": "unique-value", "Cache-Control": "max-age=3600"}
return JSONResponse(content=content, headers=headers)
You can check the response headers in your browser's developer tools or using tools like curl:
curl -i http://localhost:8000/headers/
Output will include:
HTTP/1.1 200 OK
x-custom-header: unique-value
cache-control: max-age=3600
...
Setting Cookies
You can set cookies in your response:
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/cookie/")
async def create_cookie():
content = {"message": "Cookie has been set"}
response = JSONResponse(content=content)
response.set_cookie(key="cookie-name", value="cookie-value", max_age=3600)
return response
FileResponse for Serving Files
To serve files from your API:
from fastapi.responses import FileResponse
@app.get("/file/")
async def get_file():
file_path = "example.txt" # Path to your file
return FileResponse(
path=file_path,
filename="downloaded.txt",
media_type="text/plain"
)
StreamingResponse for Large Data
For large files or data streams that shouldn't be loaded entirely into memory:
from fastapi.responses import StreamingResponse
import io
@app.get("/stream/")
async def get_streaming_response():
def generate_numbers():
for i in range(1, 11):
yield f"Number: {i}\n"
return StreamingResponse(generate_numbers(), media_type="text/plain")
RedirectResponse
Redirect users to another URL:
from fastapi.responses import RedirectResponse
@app.get("/redirect/")
async def redirect():
return RedirectResponse(url="https://fastapi.tiangolo.com")
Response Models vs Response Objects
It's important to distinguish between Response Models (defining the structure of your data) and Response Objects (controlling how that data is sent):
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
app = FastAPI()
class Item(BaseModel):
id: int
name: str
description: str = None
@app.get("/items/", response_model=List[Item])
async def get_items():
# The response will be validated against the Item model
items = [
{"id": 1, "name": "Foo"},
{"id": 2, "name": "Bar", "description": "This is an item"}
]
return items
Real-World Example: API with Custom Responses
Let's create a more practical example of an API endpoint that returns different responses based on conditions:
from fastapi import FastAPI, HTTPException, Header
from fastapi.responses import JSONResponse, FileResponse, RedirectResponse
from typing import Optional
app = FastAPI()
@app.get("/api/resources/{resource_id}")
async def get_resource(
resource_id: int,
format: Optional[str] = None,
user_agent: Optional[str] = Header(None)
):
# Validate resource exists
if resource_id > 100:
raise HTTPException(status_code=404, detail="Resource not found")
# Return different formats based on query param
if format == "pdf":
return FileResponse(
path="resources/sample.pdf",
filename=f"resource_{resource_id}.pdf",
media_type="application/pdf"
)
# Redirect mobile users to mobile version
if user_agent and "mobile" in user_agent.lower():
return RedirectResponse(url=f"/mobile/resources/{resource_id}")
# Default JSON response
resource_data = {
"id": resource_id,
"name": f"Resource {resource_id}",
"description": "This is a sample resource",
"created_at": "2023-01-01T12:00:00Z"
}
# Custom headers for caching
headers = {
"Cache-Control": "max-age=3600",
"X-Resource-ID": str(resource_id)
}
return JSONResponse(
content=resource_data,
headers=headers
)
Handling Errors with Response Objects
Custom error responses are important for providing clear information to API consumers:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
class CustomError(Exception):
def __init__(self, name: str, code: str, message: str):
self.name = name
self.code = code
self.message = message
@app.exception_handler(CustomError)
async def custom_error_handler(request: Request, exc: CustomError):
return JSONResponse(
status_code=400,
content={
"error": exc.name,
"code": exc.code,
"message": exc.message,
"path": request.url.path
}
)
@app.get("/items/{item_id}")
async def get_item(item_id: int):
if item_id == 0:
raise CustomError(
name="InvalidItemError",
code="ITEM_0_NOT_ALLOWED",
message="Item ID 0 is not a valid ID"
)
return {"item_id": item_id}
If you access /items/0
, you'll get:
{
"error": "InvalidItemError",
"code": "ITEM_0_NOT_ALLOWED",
"message": "Item ID 0 is not a valid ID",
"path": "/items/0"
}
Summary
Response objects in FastAPI provide fine-grained control over your API's HTTP responses. They allow you to:
- Return different content types (JSON, HTML, plain text, files)
- Set status codes, headers, and cookies
- Stream large responses
- Redirect users to different URLs
- Handle errors with custom responses
By mastering response objects, you can create more robust and flexible APIs that communicate effectively with clients.
Additional Resources
Exercises
- Create an endpoint that returns a CSV file generated from Python data.
- Build an endpoint that returns different responses based on an "Accept" header (JSON, XML, or HTML).
- Create a streaming endpoint that simulates a long-running process, sending updates as they occur.
- Implement a caching system using response headers that updates the cache max-age based on how often the resource changes.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)