FastAPI Response Types
When building APIs with FastAPI, understanding how to properly handle different response types is crucial for creating robust and well-documented applications. FastAPI provides flexible ways to return various forms of data to clients, from simple JSON responses to file downloads and custom response models.
Introduction to Response Types
In web applications, the server needs to send back different kinds of responses depending on the client's request - sometimes you'll send JSON data, other times HTML content, binary files, or even redirect the client to another URL. FastAPI makes this process straightforward by providing different response types for various scenarios.
FastAPI provides two main ways to define response types:
- Using
Response
objects directly - Using response model type hints with Pydantic models
Let's explore both approaches and see the different response types available.
Basic Response Objects
FastAPI includes several response classes that inherit from Starlette's Response
class. These allow you to have fine-grained control over your API responses.
JSONResponse
The most common type of response in REST APIs is JSON. FastAPI automatically converts your return values to JSON, but you can also explicitly use JSONResponse
:
from fastapi import FastAPI, Response, JSONResponse
app = FastAPI()
@app.get("/items/")
def read_items():
return JSONResponse(
content={"message": "Hello World", "status": "success"},
status_code=200,
)
Output:
{
"message": "Hello World",
"status": "success"
}
PlainTextResponse
When you need to return simple text:
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.get("/", response_class=PlainTextResponse)
def read_root():
return "Hello World"
Output:
Hello World
Notice how we used response_class=PlainTextResponse
to tell FastAPI to use this response type.
HTMLResponse
When you need to return HTML content:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/html/", response_class=HTMLResponse)
def read_html():
return """
<html>
<head>
<title>FastAPI HTML Example</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
"""
This will return a response with the content type set to text/html
.
RedirectResponse
When you need to redirect users to a different URL:
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/redirect/")
async def redirect():
return RedirectResponse("https://fastapi.tiangolo.com")
This will redirect the client to the FastAPI documentation site.
FileResponse
For serving files:
from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()
@app.get("/get-file/")
async def get_file():
return FileResponse("./files/example.pdf",
filename="downloaded_example.pdf",
media_type="application/pdf")
This serves a PDF file from your server and sets the appropriate headers for downloading.
StreamingResponse
For streaming large content without loading it entirely into memory:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import io
app = FastAPI()
async def fake_large_data_generator():
for i in range(10):
yield f"chunk {i}\n".encode()
# In a real app, you might be reading from a large file
@app.get("/stream/")
async def stream_response():
return StreamingResponse(fake_large_data_generator())
This is particularly useful for large files or real-time data streams.
Using Response Models with Pydantic
One of FastAPI's powerful features is its integration with Pydantic for data validation. You can define response models to:
- Validate your returned data
- Automatically generate documentation for your API
- Filter returned data
Here's how to use response models:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
class Item(BaseModel):
id: int
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: List[str] = []
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
# Process the item (e.g., save to database)
return item
Response Model with Data Filtering
You can use response models to filter out fields from the returned data:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: str
full_name: str = None
class UserOut(BaseModel):
username: str
email: str
full_name: str = None
@app.post("/users/", response_model=UserOut)
async def create_user(user: UserIn):
# Notice that we're returning UserIn but FastAPI will
# filter it to match UserOut, removing the password
return user
In this example, even though we return the full user
object that includes the password, FastAPI will only include the fields defined in UserOut
in the response, effectively filtering out the password.
Setting Custom Status Codes and Headers
You can customize HTTP status codes and headers in multiple ways:
Using 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}
Using Response Objects with Custom Headers
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/custom-headers/")
def get_custom_headers():
content = {"message": "Hello World"}
headers = {"X-Custom-Header": "custom-value", "X-Another-Header": "another-value"}
return JSONResponse(content=content, headers=headers)
Real-world Examples
API Endpoint with Proper Error Handling
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
from typing import List, Optional, Dict
app = FastAPI()
# Fake database
fake_db = {}
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
class ItemResponse(BaseModel):
id: int
name: str
description: Optional[str] = None
price: float
@app.post("/items/", response_model=ItemResponse, status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
# Generate a new ID
item_id = len(fake_db) + 1
# Store in the fake database
fake_db[item_id] = item.dict()
# Return the item with its ID
return {"id": item_id, **item.dict()}
@app.get("/items/{item_id}", response_model=ItemResponse)
async def read_item(item_id: int):
if item_id not in fake_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Item with ID {item_id} not found"
)
return {"id": item_id, **fake_db[item_id]}
File Download API
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
import os
app = FastAPI()
@app.get("/download/{file_name}")
async def download_file(file_name: str):
file_path = f"./files/{file_name}"
if not os.path.isfile(file_path):
raise HTTPException(status_code=404, detail="File not found")
# Determine content type based on extension
extension = file_name.split(".")[-1].lower()
media_type = None
if extension == "pdf":
media_type = "application/pdf"
elif extension in ["jpg", "jpeg"]:
media_type = "image/jpeg"
elif extension == "png":
media_type = "image/png"
return FileResponse(
path=file_path,
filename=file_name,
media_type=media_type
)
API with Dynamic Response Types
from fastapi import FastAPI, Query
from fastapi.responses import JSONResponse, HTMLResponse, PlainTextResponse
app = FastAPI()
data = {
"items": [
{"id": 1, "name": "Hammer"},
{"id": 2, "name": "Screwdriver"},
{"id": 3, "name": "Wrench"}
]
}
@app.get("/items/")
async def get_items(format: str = Query("json", regex="^(json|html|text)$")):
"""
Get items in different formats based on the format query parameter.
- format=json: Returns JSON data
- format=html: Returns HTML table
- format=text: Returns plain text
"""
if format == "json":
return JSONResponse(content=data)
elif format == "html":
html_content = """
<html>
<head>
<title>Items</title>
<style>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid black; padding: 8px; text-align: left; }
</style>
</head>
<body>
<h1>Items</h1>
<table>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
"""
for item in data["items"]:
html_content += f"""
<tr>
<td>{item['id']}</td>
<td>{item['name']}</td>
</tr>
"""
html_content += """
</table>
</body>
</html>
"""
return HTMLResponse(content=html_content)
elif format == "text":
text_content = "ITEMS\n-----\n"
for item in data["items"]:
text_content += f"ID: {item['id']}, Name: {item['name']}\n"
return PlainTextResponse(content=text_content)
Summary
FastAPI offers a wide range of response types to handle various API requirements:
- JSONResponse - For returning JSON data (default in FastAPI)
- PlainTextResponse - For simple text responses
- HTMLResponse - For HTML content
- RedirectResponse - For redirecting clients
- FileResponse - For serving files
- StreamingResponse - For streaming large content
Additionally, you can use Pydantic models as response_model parameters to:
- Validate response data
- Filter out sensitive fields
- Generate automatic API documentation
- Convert returned data to match the response model
Understanding these different response types allows you to build more flexible and powerful APIs that can handle various client needs, from simple data exchanges to file downloads and streaming content.
Additional Resources
Exercises
-
Create an API endpoint that can return a list of products in three different formats: JSON, CSV, and XML based on a query parameter.
-
Build a file management API with endpoints to:
- Upload a file
- List all uploaded files
- Download a specific file
- Delete a file
-
Create an API endpoint that simulates a long-running process and streams the progress back to the client using StreamingResponse.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)