Skip to main content

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:

  1. Using Response objects directly
  2. 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:

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

json
{
"message": "Hello World",
"status": "success"
}

PlainTextResponse

When you need to return simple text:

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

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

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

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

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

  1. Validate your returned data
  2. Automatically generate documentation for your API
  3. Filter returned data

Here's how to use response models:

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

python
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

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}

Using Response Objects with Custom Headers

python
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

python
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

python
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

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

  1. JSONResponse - For returning JSON data (default in FastAPI)
  2. PlainTextResponse - For simple text responses
  3. HTMLResponse - For HTML content
  4. RedirectResponse - For redirecting clients
  5. FileResponse - For serving files
  6. 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

  1. Create an API endpoint that can return a list of products in three different formats: JSON, CSV, and XML based on a query parameter.

  2. Build a file management API with endpoints to:

    • Upload a file
    • List all uploaded files
    • Download a specific file
    • Delete a file
  3. 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! :)