Skip to main content

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

python
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

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

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

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

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/json/")
async def get_json():
return {"message": "This is JSON"}

HTML Responses

python
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

python
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

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

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

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

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

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

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

json
{
"status": "success",
"message": "User retrieved successfully",
"data": {
"id": 123,
"name": "John Doe",
"email": "[email protected]"
}
}

When calling /users/404, you'll get:

json
{
"status": "error",
"message": "User not found",
"data": null
}

Background Tasks with Responses

Sometimes you need to perform actions after returning a response:

python
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

  1. Create an API endpoint that returns user data as JSON but adds a custom X-RateLimit-Remaining header.
  2. Implement a custom response class that automatically adds HATEOAS links to your resources.
  3. Build an endpoint that returns different content types (HTML, JSON, XML) based on an Accept header.
  4. Create a file download endpoint that streams a large file and sets appropriate headers.
  5. 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! :)