Sling Academy
Home/FastAPI/FastAPI: How to Create a Custom 404 (Not Found) Route

FastAPI: How to Create a Custom 404 (Not Found) Route

Last updated: August 30, 2023

404 is an HTTP status code that indicates that a server couldn’t find the resource requested by a client (such as a web browser or API client). FastAPI automatically handles this error for us, however, its response is plain and doesn’t ship much helpful information, as shown in the screenshot below:

This concise, practical article will walk you through 2 different examples of implementing a custom 404 route and a custom 404 HTML page in FastAPI.

What is the Point?

In general, the steps to get the job done are as follows:

1. Import HTTPException from fastapi and Request from starlette.

2. Create an instance of FastAPI and define your normal routes as usual.

3. Define a function that will handle the 404 error. This function should take two parameters: request and exc. The request is the incoming request object and the exc is the exception object that contains the status code and the detail message. In this function, you can return any response you want, such as a JSON response, an HTML response, or a redirect response. For example, you can return a JSON response with a custom message like this:

def not_found_error(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=404,
        content={"message": "Oops! The resource you requested was not found."},
    )

5. Register your function as an exception handler for the status code 404 using the exception_handler decorator or the exception_handlers parameter of the FastAPI class. For instance, you can use the decorator like this:

@app.exception_handler(404)
def not_found_exception_handler(request: Request, exc: HTTPException):
    return not_found_error(request, exc)

Alternatively, you can use the parameter like this:

app = FastAPI(exception_handlers={404: not_found_error})

Examples

Creating a Custom 404 Route (JSON Response)

In this example, we’ll build a tiny REST API that returns a dummy product by its ID. If a product is found, you’ll see this:

In case the requested product doesn’t exist, we’ll send a custom message to the client:

The full code:

from fastapi import FastAPI, HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse

app = FastAPI()

# Dummy products data
products = [
    {"id": 1, "name": "Cheese", "price": 4.5},
    {"id": 2, "name": "Brick", "price": 2.5},
]


# Define a route to get a single product by id
@app.get("/products/{product_id}")
async def get_product(product_id: str):
    # Find product by id
    product = next((p for p in products if p["id"] == product_id), None)

    # Raise an HTTPException with a 404 if product wasn't found
    if product is None:
        raise HTTPException(
            status_code=404,
            detail="Product not found. Please try again with a valid product id.",
        )

    # Return the product as JSON if it was found
    return {"success": True, "data": product}


# Define a custom error handler for 404
def not_found_error(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=404,
        content={
            "success": False,
            "message": exc.detail or "The requested resource was not found."
        },
    )


# Register the error handler using the app.exception_handler decorator
@app.exception_handler(404)
def not_found_exception_handler(request: Request, exc: HTTPException):
    return not_found_error(request, exc)

Creating a Custom 404 HTML Page

This example is similar to the previous one, but instead of returning JSON data, our FastAPI application will serve an HTML web page when the requested resource is not found:

Most of the code is the same as in the preceding example except 2 points:

1. HTMLResponse is imported instead of JSONResponse.

2. The not_found_error() function is updated as follows:

def not_found_error(request: Request, exc: HTTPException):
    return HTMLResponse(
        status_code=404,
        content=f"""
        <html>

        <head>
            <title>404 Not Found</title>
        </head>

        <body style="padding: 30px">
            <h1>404 Not Found</h1>
            <p>The resource could not be found.</p>
        </body>

        </html>
        """,
    )

The complete code:

from fastapi import FastAPI, HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse

app = FastAPI()

# Dummy products data
products = [
    {"id": 1, "name": "Cheese", "price": 4.5},
    {"id": 2, "name": "Brick", "price": 2.5},
]


# Define a route to get a single product by id
@app.get("/products/{product_id}")
async def get_product(product_id: int):
    # Find product by id
    product = next((p for p in products if p["id"] == product_id), None)

    # Raise an HTTPException with a 404 if product wasn't found
    if product is None:
        raise HTTPException(
            status_code=404,
            detail="Product not found. Please try again with a valid product id.",
        )

    # Return the product as JSON if it was found
    return {"success": True, "data": product}


# Define a custom error handler for 404
def not_found_error(request: Request, exc: HTTPException):
    return HTMLResponse(
        status_code=404,
        content=f"""
        <html>

        <head>
            <title>404 Not Found</title>
        </head>

        <body style="padding: 30px">
            <h1>404 Not Found</h1>
            <p>The resource could not be found.</p>
        </body>

        </html>
        """,
    )


# Register the error handler using the app.exception_handler decorator
@app.exception_handler(404)
def not_found_exception_handler(request: Request, exc: HTTPException):
    return not_found_error(request, exc)

Conclusion

In summary, creating a custom 404 route/page in FastAPI is simple and flexible. You just need to define a function that returns the desired response and register it as an exception handler for the status code 404. You can also customize the response for other status codes using the same technique.

This tutorial ends here. If you find something incorrect in the code snippets, please let me know by leaving a comment. Happy coding & have a nice day!

Next Article: FastAPI: How to use macros in Jinja templates

Previous Article: How to Run FastAPI on a Custom Port

Series: FastAPI Tutorials for Beginners

FastAPI

You May Also Like

  • Popular useful built-in Jinja filters you should know
  • How to remove consecutive whitespace in rendered Jinja pages
  • How to format large numbers with thousand separators in Jinja template
  • How to format date time in Jinja templates
  • FastAPI + Jinja: How to create custom filters
  • How to pass variables from Python (FastAPI) to Jinja
  • How to decode Jinja response to string
  • How to create and use macros in Jinja
  • How to use namespace in Jinja
  • How to use if/ else in Jinja
  • How to use loops in Jinja
  • FastAPI + SQLAlchemy: Using cursor-based pagination
  • FastAPI: How to use macros in Jinja templates
  • Fixing Common Swagger UI Errors in FastAPI
  • FastAPI Error: 307 Temporary Redirect – Causes and Solutions
  • FastAPI Error: Expected UploadFile, received ‘str’
  • Resolving FastAPI ImportError: No Known Parent Package
  • FastAPI Error: No module named ‘pydantic_core._pydantic_core’
  • Resolving FastAPI 422 Error: Value is not a valid dict