Effortless Exception Error Handling in FastAPI: A Clean and Simplified Approach

Implementing a middleware that displays the errors in a more concise and organized manner.

Updated: Aug, 24, 2023

I wanted clarify that the provided code is specifically tailored for usage within a local or staging development environment.

As pointed by serban t in the comment section that exposing internal error details and sending verbose error messages to the client can indeed be risky and provide attackers with potential information about the application’s architecture and vulnerabilities.

And following the recommendation of Alexandre Campino, I have incorporated the code below to ensure the readiness of the solution for production deployment.

After deploying my project, I encountered difficulty in effectively identifying exception errors, especially when they included lengthy stack traces. Enabling debug=True didn’t provide the desired help, as it cluttered the output with unnecessary information and is not advisable for production environments.

To address this issue, I explored various approaches to handle the errors and eventually settled on a much cleaner solution: implementing a middleware that displays the errors in a more concise and organized manner like this:

Instead of just like the image below when debug=False which is default when initializing FastAPI:

Or like below if you set debug=True

Warning! The sentences below has been turbocharged and supercharged by the one and only Chat-GPT! Brace yourself for the awesomeness it exudes, and remember, you’re now equipped with the power of AI-generated brilliance! 😎

So I set out on a noble quest to conquer all the pesky exception errors, declaring war on the messy and repetitive try-catch blocks that threatened to overrun my codebase. With a single mighty handler, I banished those error monsters to the abyss and now revel in the sweet serenity of clean and elegant code.


💡 Also, learn how to create APIs in Python using Flask:

How to Create APIs in Python Using Flask: GET and POST Method

👉 To read more such acrticles, sign up for free on Differ.


Creating a Middleware

I won’t delve into the mystical depths of middleware here — if you’re curious, let Google be your guide 😅.

Now, let me reveal my project’s directory structure, carefully crafted with files neatly laid out like my favorite superhero action figures. Reading long and tiring statements in one file? Not my style! Oh, and just so you know, I’m an unapologetic Laravel enthusiast 😁.

|- main.py
|- __init__.py
  |- middlewares
    |- __init__.py
    |- exception.py
  |- router
    | __init__.py
    |- api_v1
    |- routes.py
... more directories ...

Just follow these steps:

  1. Create a new directory named **middlewares** in the root project.
  2. Then create a new file named **exception.py** or whatever you want.
  3. Then copy the code below:
from traceback import print_exception

from fastapi import Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware

class ExceptionHandlerMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        try:
            return await call_next(request)
        except Exception as e:
            print_exception(e)
            return JSONResponse(
                status_code=500,
                content={
                    'error': e.__class__.__name__,
                    'messages': e.args
                }
            )

Behold, the code above is like a vigilant guardian angel for FastAPI, dressed in Starlette’s finest armor! This noble middleware fearlessly intercepts incoming requests, dances with route handlers in the background, and even manages to catch those mischievous exceptions that dare to disrupt the harmony. And when an exception is caught red-handed, this valiant knight crafts a JSON response, revealing the secrets of the error to the world! All hail the Exception Handler Middleware, the hero we didn’t know we needed! 🦸‍♂️💻

Now, prepare yourself for a majestic unveiling of the code’s inner mysteries:

  1. from traceback import print_exception: This import is used to print the exception traceback if an exception occurs.
  2. from fastapi import Request: Importing Request from FastAPI, which represents an HTTP request.
  3. from fastapi.responses import JSONResponse: Importing JSONResponse from FastAPI, which is used to create a JSON response.
  4. from starlette.middleware.base import BaseHTTPMiddleware: Importing the base middleware class from Starlette, which will be used as a base class for the custom middleware.
  5. class ExceptionHandlerMiddleware(BaseHTTPMiddleware): Defining the custom exception handler middleware class. It inherits from BaseHTTPMiddleware.
  6. async def dispatch(self, request: Request, call_next): Overriding the dispatch method from the BaseHTTPMiddleware. This method is called when a request is received.
  7. try...except block: The try block contains the code that will be executed during normal handling of the request. The except block catches any exceptions that might occur during the handling process.
  8. print_exception(e): If an exception occurs, this line prints the exception traceback to the console.
  9. JSONResponse: If an exception is caught, this line creates a JSON response with the status code 500 (Internal Server Error) and includes information about the error in the response body.
  10. 'error': e.__class__.__name__: The name of the exception class (e.g., 'ValueError', 'TypeError') is included in the JSON response.
  11. 'messages': e.args: The arguments of the exception are included in the JSON response. These arguments usually contain a description of the error and any additional information passed when the exception was raised.

Note: Printing exceptions to the console in a production environment? Well, that’s like using a foghorn to announce a tea party — it might work, but it’s not exactly elegant. In the realm of production, it’s wiser to log those pesky exceptions to a file or a monitoring system, leaving the console for the important stuff, like cat videos and memes.

Then add this line in your main.py

from middlewares.exception import ExceptionHandlerMiddleware

app.add_middleware(ExceptionHandlerMiddleware)

And voilà! With this nifty setup, all exceptions will be effortlessly handled and returned as responses, making them as visible and accessible as your favorite meme collection. No more digging through cryptic logs or chasing elusive bugs.

Now, sit back, relax, and enjoy the smooth sailing with your error-free application!

Now with all the bugs squashed and ready to shine brighter than a supernova in the coding galaxy! This is the production ready code:

import logging
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware

logger = logging.getLogger(__name__)

class ExceptionHandlerMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        try:
            return await call_next(request)
        except HTTPException as http_exception:
            return JSONResponse(
                status_code=http_exception.status_code,
                content={"error": "Client Error", "message": str(http_exception.detail)},
            )
        except Exception as e:
            logger.exception(msg=e.__class__.__name__, args=e.args)
            return JSONResponse(
                status_code=500,
                content={"error": "Internal Server Error", "message": "An unexpected error occurred."},
            )

That's it for this topic. Thank you for reading!

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics