Skip to content

Custom Maintenance Responses

FastAPI Maintenance allows you to fully customize the response returned when maintenance mode is active. This is useful for providing better user experiences, custom maintenance pages, or specialized response formats.

Default Response

By default, when maintenance mode is active, the middleware returns a JSON response with:

  • Status code: 503 Service Unavailable
  • Headers: Retry-After: 3600 (suggesting clients retry after 1 hour)
  • Body: {"detail": "Service temporarily unavailable due to maintenance"}

Customization

You can customize the maintenance response by providing a response_handler function to the middleware. This function can return any valid FastAPI/Starlette response. Here's what you need to know:

Function Signature

The response handler function must: - Accept a single Request parameter from FastAPI - Return a valid FastAPI/Starlette Response object - Be either synchronous or asynchronous (both are supported)

# Synchronous handler
def maintenance_response(request: Request) -> Response:
    # Create and return a custom response
    return JSONResponse(
        content={"status": "maintenance", "retry_after": "30 minutes"},
        status_code=503,
        headers={"Retry-After": "1800"}
    )

# Asynchronous handler
async def maintenance_response(request: Request) -> Response:
    # Create and return a custom response
    return JSONResponse(
        content={"status": "maintenance", "retry_after": "30 minutes"},
        status_code=503,
        headers={"Retry-After": "1800"}
    )

Return Value

The handler can use any FastAPI/Starlette response type such as JSONResponse, HTMLResponse, RedirectResponse, PlainTextResponse, FileResponse, or custom responses. You can also use the request information to customize the response.

Basic Usage

Here's a simple example that customizes the JSON response:

from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
from fastapi_maintenance import MaintenanceModeMiddleware

app = FastAPI()

async def custom_maintenance_response(request: Request) -> JSONResponse:
    return JSONResponse(
        status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
        content={
            "error": "Maintenance in progress",
            "retry_after": "10 minutes",
            "more_info": "https://status.example.com"
        },
        headers={"Retry-After": "600"}  # 10 minutes
    )

app.add_middleware(
    MaintenanceModeMiddleware,
    response_handler=custom_maintenance_response
)

Examples

HTML Maintenance Page

You can return an HTML maintenance page instead of JSON:

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi_maintenance import MaintenanceModeMiddleware

app = FastAPI()

async def html_maintenance_page(request: Request) -> HTMLResponse:
    html_content = """
    <!DOCTYPE html>
    <html>
        <head>
            <title>Site Maintenance</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <style>
                body {
                    font-family: Arial, sans-serif;
                    line-height: 1.6;
                    margin: 0;
                    padding: 30px;
                    text-align: center;
                    color: #333;
                }
                .container {
                    max-width: 600px;
                    margin: 0 auto;
                    padding: 20px;
                    border: 1px solid #ddd;
                    border-radius: 5px;
                    background-color: #f9f9f9;
                }
                h1 {
                    color: #e74c3c;
                }
                .status-time {
                    font-style: italic;
                    color: #7f8c8d;
                    margin-top: 20px;
                }
            </style>
        </head>
        <body>
            <div class="container">
                <h1>Site Under Maintenance</h1>
                <p>We're currently performing scheduled maintenance to improve our services.</p>
                <p>Please check back soon. We apologize for any inconvenience.</p>
                <p class="status-time">Expected completion: 2 hours</p>
            </div>
        </body>
    </html>
    """
    return HTMLResponse(
        content=html_content,
        status_code=503,
        headers={"Retry-After": "7200"}  # 2 hours
    )

app.add_middleware(
    MaintenanceModeMiddleware,
    response_handler=html_maintenance_page
)

Content Negotiation

You can implement content negotiation to return different formats based on the request's Accept header:

from fastapi import FastAPI, Request, Response, status
from fastapi.responses import JSONResponse, HTMLResponse, PlainTextResponse
from fastapi_maintenance import MaintenanceModeMiddleware

app = FastAPI()

async def content_negotiated_response(request: Request) -> Response:
    accept = request.headers.get("accept", "")

    # Return HTML for browser requests
    if "text/html" in accept:
        html_content = "<html><body><h1>Under Maintenance</h1><p>Please check back later.</p></body></html>"
        return HTMLResponse(content=html_content, status_code=503)

    # Return plain text for text requests
    if "text/plain" in accept:
        return PlainTextResponse(content="Service under maintenance. Please check back later.", status_code=503)

    # Default to JSON
    return JSONResponse(
        content={"status": "maintenance", "message": "Service temporarily unavailable"},
        status_code=status.HTTP_503_SERVICE_UNAVAILABLE
    )

app.add_middleware(
    MaintenanceModeMiddleware,
    response_handler=content_negotiated_response
)

Using Templates

You can integrate with FastAPI's template system for more sophisticated HTML responses:

from fastapi import FastAPI, Request, Response
from fastapi.templating import Jinja2Templates
from fastapi_maintenance import MaintenanceModeMiddleware

app = FastAPI()

# Set up templates
templates = Jinja2Templates(directory="templates")

async def template_maintenance_page(request: Request) -> Response:
    # Pass data to the template
    return templates.TemplateResponse(
        "maintenance.html",
        {"request": request, "site_name": "My API", "estimated_time": "2 hours"},
        status_code=503
    )

app.add_middleware(
    MaintenanceModeMiddleware,
    response_handler=template_maintenance_page
)

With a template file at templates/maintenance.html:

<!DOCTYPE html>
<html>
<head>
    <title>{{ site_name }} - Maintenance</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        /* Your CSS styles here */
    </style>
</head>
<body>
    <div class="container">
        <h1>{{ site_name }} is under maintenance</h1>
        <p>We're working on improving our services. Please check back soon.</p>
        <p>Estimated completion time: {{ estimated_time }}</p>
    </div>
</body>
</html>

Using Request Information

The handler function receives the request object, so you can customize responses based on the request parameters:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi_maintenance import MaintenanceModeMiddleware

app = FastAPI()

async def path_aware_response(request: Request) -> JSONResponse:
    # Customize message based on path
    path = request.url.path

    if path.startswith("/api/v1"):
        message = "API v1 is undergoing maintenance. Please use API v2."
    elif path.startswith("/api/v2"):
        message = "API v2 is being updated. Please try again later."
    else:
        message = "Service temporarily unavailable due to maintenance."

    return JSONResponse(
        content={"detail": message, "path": path},
        status_code=503
    )

app.add_middleware(
    MaintenanceModeMiddleware,
    response_handler=path_aware_response
)