Skip to main content

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:

python
@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:

  1. Set specific status codes
  2. Set custom headers
  3. Return specific content types
  4. Set cookies
  5. Send streaming responses
  6. 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.

python
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:

json
{
"message": "This is a custom JSON response"
}

PlainTextResponse

For returning plain text:

python
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:

python
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:

python
@app.get("/items/", status_code=201)
async def create_item():
return {"message": "Item created"}

Using a Response object:

python
@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:

python
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:

bash
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:

python
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:

python
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:

python
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:

python
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):

python
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:

python
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:

python
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:

json
{
"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

  1. Create an endpoint that returns a CSV file generated from Python data.
  2. Build an endpoint that returns different responses based on an "Accept" header (JSON, XML, or HTML).
  3. Create a streaming endpoint that simulates a long-running process, sending updates as they occur.
  4. 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! :)