Advanced FastAPI Patterns • Lesson 8

Custom Response Types

Learn to use different response classes in FastAPI including HTMLResponse, JSONResponse with custom headers, and RedirectResponse.

🎯 What You'll Learn

  • Understand the different response classes available in FastAPI
  • Return HTML content using HTMLResponse and response_class
  • Add custom headers to JSON responses using JSONResponse
  • Implement redirects using RedirectResponse

Custom Response Types

🎯 What You'll Learn

  • How to return HTML content using HTMLResponse
  • How to add custom headers to JSON responses using JSONResponse
  • How to implement redirects using RedirectResponse
  • An overview of other response types like StreamingResponse and FileResponse

📚 Theory

By default, FastAPI returns responses using JSONResponse. However, there are many situations where you need to return different types of content -- HTML pages, files, redirects, or JSON with custom headers. FastAPI provides several response classes for these scenarios, all available from fastapi.responses.

HTMLResponse

When you need to return HTML content directly from an endpoint, use HTMLResponse. Set response_class=HTMLResponse in the decorator so FastAPI generates the correct OpenAPI documentation and sets the proper Content-Type header.

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/page/", response_class=HTMLResponse)
async def get_page():
    return "<html><body><h1>Hello!</h1></body></html>"

The response_class parameter tells FastAPI that this endpoint returns HTML, not JSON. This affects both the documentation and the Content-Type header (text/html instead of application/json).

JSONResponse with Custom Headers

While FastAPI automatically uses JSONResponse for dict/list returns, sometimes you need more control. By returning a JSONResponse directly, you can set custom headers, change the status code, or modify other response properties.

from fastapi.responses import JSONResponse

@app.get("/data/")
async def get_data():
    return JSONResponse(
        content={"key": "value"},
        headers={"X-Custom-Header": "my-value"},
        status_code=200,
    )

Custom headers are useful for:

  • Rate limiting information (X-RateLimit-Remaining)
  • Request tracing (X-Request-ID)
  • Cache control directives
  • Custom application metadata

RedirectResponse

To redirect clients to a different URL, use RedirectResponse. By default it sends a 307 (Temporary Redirect) status code, preserving the HTTP method.

from fastapi.responses import RedirectResponse

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

You can change the status code if needed:

  • 301 - Permanent redirect (search engines update their index)
  • 302 - Found (temporary redirect, may change method to GET)
  • 307 - Temporary redirect (preserves HTTP method)
  • 308 - Permanent redirect (preserves HTTP method)
@app.get("/moved/")
async def moved_permanently():
    return RedirectResponse(url="/new-location/", status_code=301)

StreamingResponse (Concept)

For large content that you want to send incrementally, StreamingResponse accepts an async generator or iterator. This is ideal for large files or real-time data.

from fastapi.responses import StreamingResponse

async def generate_data():
    for i in range(100):
        yield f"chunk {i}\n"

@app.get("/stream/")
async def stream():
    return StreamingResponse(generate_data(), media_type="text/plain")

StreamingResponse is also the foundation for Server-Sent Events (SSE), a technique for pushing real-time updates from server to client. SSE uses text/event-stream as the media type and follows a specific format for events.

FileResponse (Concept)

To serve files directly, FileResponse takes a file path and handles reading and streaming the file content. Note that in browser-based environments (like Pyodide), file system access is limited.

from fastapi.responses import FileResponse

@app.get("/download/")
async def download():
    return FileResponse("report.pdf", filename="report.pdf")

The response_class Parameter

The response_class parameter on the path operation decorator serves two purposes:

  1. Sets the default response class for the endpoint
  2. Documents the response type in the OpenAPI schema
@app.get("/page/", response_class=HTMLResponse)
async def get_page():
    # Can return a string -- it will be wrapped in HTMLResponse
    return "<h1>Hello</h1>"

When response_class is set, you can return the raw content (like a string for HTML) and FastAPI wraps it automatically.

🔧 Key Concepts

  • Response Classes: Specialized classes that control content type, headers, and behavior
  • HTMLResponse: Returns HTML content with text/html content type
  • JSONResponse: Returns JSON with full control over headers and status code
  • RedirectResponse: Sends clients to a different URL with appropriate status codes
  • StreamingResponse: Sends content in chunks for large or real-time data
  • FileResponse: Serves files from the file system
  • response_class: Decorator parameter that sets default response type and OpenAPI docs
  • Server-Sent Events: A pattern using StreamingResponse for real-time server-to-client updates

💡 Best Practices

  • Use response_class in the decorator when an endpoint always returns a specific type
  • Return JSONResponse directly only when you need custom headers or status codes
  • Choose the correct redirect status code (301 vs 307) based on permanence and method preservation
  • Use StreamingResponse for large payloads to avoid loading everything into memory
  • Set appropriate media_type on streaming responses for correct client handling
  • Keep HTML responses simple -- for complex pages, consider a template engine like Jinja2

🔗 Additional Resources

💡 Hint

Import response classes from fastapi.responses. Use response_class=HTMLResponse in the decorator for HTML endpoints. For custom headers, return a JSONResponse object directly with the headers parameter.

Ready to Practice?

Now that you understand the theory, let's put it into practice with hands-on coding!

Start Interactive Lesson