Skip to main content
FastAPI provides several response classes for returning different types of content. These classes are built on top of Starlette’s response classes and allow you to customize the response sent to the client.

Importing

from fastapi.responses import (
    Response,
    JSONResponse,
    HTMLResponse,
    PlainTextResponse,
    RedirectResponse,
    StreamingResponse,
    FileResponse,
    EventSourceResponse,
)

Response Classes

Response

The base response class. Use this for custom responses or when you need full control.Constructor Parameters:
content
bytes | str
default:"b''"
The response body content.
status_code
int
default:"200"
HTTP status code.
headers
dict[str, str] | None
default:"None"
HTTP headers.
media_type
str | None
default:"None"
Media type (Content-Type).
background
BackgroundTask | None
default:"None"
Background task to run after sending the response.
from fastapi import FastAPI
from fastapi.responses import Response

app = FastAPI()

@app.get("/custom")
async def custom_response():
    return Response(
        content="Custom content",
        media_type="text/plain",
        status_code=200,
        headers={"X-Custom-Header": "value"},
    )
Attributes:
status_code
int
The HTTP status code.
headers
MutableHeaders
Response headers.
body
bytes
The response body.
media_type
str | None
The media type (Content-Type).
background
BackgroundTask | None
Background task to execute after response.
Methods:
  • set_cookie() - Set a cookie
  • delete_cookie() - Delete a cookie

JSONResponse

Returns a JSON response. This is the default response class in FastAPI.
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/items")
async def read_items():
    return JSONResponse(
        content={"items": ["item1", "item2"]},
        status_code=200,
        headers={"X-Custom-Header": "value"},
    )
Note: When using JSONResponse directly, the content must be JSON-serializable. FastAPI’s automatic response serialization using Pydantic models is often more convenient:
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float

@app.get("/items", response_model=Item)
async def read_item():
    return Item(name="Portal Gun", price=42.0)

HTMLResponse

Returns an HTML response.
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/", response_class=HTMLResponse)
async def read_root():
    html_content = """
    <html>
        <head>
            <title>My App</title>
        </head>
        <body>
            <h1>Hello World!</h1>
        </body>
    </html>
    """
    return html_content

# Or return HTMLResponse directly:
@app.get("/page")
async def read_page():
    return HTMLResponse(content="<h1>Hello</h1>", status_code=200)

PlainTextResponse

Returns a plain text response.
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()

@app.get("/text", response_class=PlainTextResponse)
async def read_text():
    return "Hello, World!"

# Or return PlainTextResponse directly:
@app.get("/plain")
async def plain():
    return PlainTextResponse("Plain text content")

RedirectResponse

Returns an HTTP redirect response.Constructor Parameters:
url
str
required
The URL to redirect to.
status_code
int
default:"307"
HTTP status code (307 for temporary redirect, 308 for permanent).
headers
dict[str, str] | None
default:"None"
Additional headers.
from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()

@app.get("/old-path")
async def old_path():
    return RedirectResponse(url="/new-path")

@app.get("/redirect")
async def redirect():
    return RedirectResponse(
        url="https://example.com",
        status_code=302,  # Temporary redirect
    )

StreamingResponse

Streams response content. Useful for large files, real-time data, or generated content.Constructor Parameters:
content
Iterator[bytes] | AsyncIterator[bytes]
required
An iterator or async iterator that yields bytes.
status_code
int
default:"200"
HTTP status code.
headers
dict[str, str] | None
default:"None"
HTTP headers.
media_type
str | None
default:"None"
Media type (Content-Type).
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio

app = FastAPI()

@app.get("/stream")
async def stream():
    async def generate():
        for i in range(10):
            yield f"data: {i}\n"
            await asyncio.sleep(1)
    
    return StreamingResponse(generate(), media_type="text/plain")

# Streaming a file:
@app.get("/video")
async def video():
    def iterfile():
        with open("video.mp4", "rb") as f:
            yield from f
    
    return StreamingResponse(iterfile(), media_type="video/mp4")

FileResponse

Returns a file as the response. Automatically handles file streaming and sets appropriate headers.Constructor Parameters:
path
str | Path
required
Path to the file.
status_code
int
default:"200"
HTTP status code.
headers
dict[str, str] | None
default:"None"
Additional headers.
media_type
str | None
default:"None"
Media type. If not set, it will be guessed from the file extension.
filename
str | None
default:"None"
Filename for the Content-Disposition header.
from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()

@app.get("/download")
async def download_file():
    return FileResponse(
        path="report.pdf",
        filename="monthly_report.pdf",
        media_type="application/pdf",
    )

@app.get("/image")
async def get_image():
    return FileResponse("image.png")

EventSourceResponse

Returns Server-Sent Events (SSE) for real-time streaming. This is FastAPI-specific and allows streaming events from async generators.
from fastapi import FastAPI
from fastapi.responses import EventSourceResponse
import asyncio

app = FastAPI()

@app.get("/events")
async def events():
    async def generate():
        for i in range(10):
            yield {"event": "message", "data": f"Event {i}"}
            await asyncio.sleep(1)
    
    return EventSourceResponse(generate())

# With custom event types:
@app.get("/notifications")
async def notifications():
    async def generate():
        yield {"event": "start", "data": "Stream started"}
        for i in range(5):
            yield {
                "event": "notification",
                "data": f'{"message": "Update {i}"}',
                "id": str(i),
            }
            await asyncio.sleep(2)
        yield {"event": "end", "data": "Stream ended"}
    
    return EventSourceResponse(generate())

Using Response Classes

Setting Default Response Class

You can set a default response class for the entire application or for specific routers:
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

# For the entire app:
app = FastAPI(default_response_class=ORJSONResponse)

# For a router:
from fastapi import APIRouter
router = APIRouter(default_response_class=HTMLResponse)

Declaring Response Class in Path Operation

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/items", response_class=HTMLResponse)
async def read_items():
    return "<html><body><h1>Items</h1></body></html>"

Returning Response Directly

from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/items")
async def read_items():
    return JSONResponse(
        content={"message": "Success"},
        status_code=200,
        headers={"X-Custom-Header": "value"},
    )

Setting Cookies

from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.post("/login")
async def login():
    response = JSONResponse(content={"message": "Logged in"})
    response.set_cookie(
        key="session_id",
        value="abc123",
        httponly=True,
        max_age=3600,
        secure=True,
        samesite="lax",
    )
    return response

@app.post("/logout")
async def logout():
    response = JSONResponse(content={"message": "Logged out"})
    response.delete_cookie(key="session_id")
    return response

Custom Response Headers

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["X-Process-Time"] = "0.123"
    return {"items": ["item1", "item2"]}

Background Tasks

from fastapi import FastAPI, BackgroundTasks
from fastapi.responses import JSONResponse

app = FastAPI()

def write_log(message: str):
    with open("log.txt", "a") as f:
        f.write(message + "\n")

@app.post("/send-notification")
async def send_notification(background_tasks: BackgroundTasks):
    background_tasks.add_task(write_log, "Notification sent")
    return {"message": "Notification sent in background"}

# Or with Response:
@app.post("/process")
async def process():
    response = JSONResponse(content={"status": "processing"})
    response.background = BackgroundTask(write_log, "Processing started")
    return response

Common Response Patterns

Success with Custom Status

@app.post("/items", status_code=201)
async def create_item(item: Item):
    return item

Error Response

from fastapi import HTTPException

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return items[item_id]

Custom Error Response

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id not in items:
        return JSONResponse(
            status_code=404,
            content={"error": "Item not found", "item_id": item_id},
        )
    return items[item_id]

Build docs developers (and LLMs) love