{"id":11824,"date":"2025-09-18T16:46:12","date_gmt":"2025-09-18T07:46:12","guid":{"rendered":"https:\/\/sreake.com\/?p=11824"},"modified":"2026-02-10T16:15:29","modified_gmt":"2026-02-10T07:15:29","slug":"fastapi-error-handling-basics-2","status":"publish","type":"post","link":"https:\/\/sreake.com\/en\/blog\/fastapi-error-handling-basics-2\/","title":{"rendered":"An Introduction to FastAPI Error Handling"},"content":{"rendered":"\n<p><em>This blog post is a translation of <a href=\"https:\/\/sreake.com\/blog\/fastapi-error-handling-basics\/\">a Japanese article<\/a> posted on May 8th, 2025.<\/em><\/p>\n\n\n\n<p>Hi! I\u2019m Atsushi Yasumoto (atusy) from the Sreake team.<\/p>\n\n\n\n<p>When designing an API server, it\u2019s important to handle program errors and return client or server errors. It\u2019s also crucial to output error logs for future debugging.<\/p>\n\n\n\n<p><a href=\"https:\/\/fastapi.tiangolo.com\/\">FastAPI<\/a>, the very popular Python web framework, provides a framework for error handling. The steps outlined in the official documentation (adding an exception handler) can cover most use cases. However, this is not well-suited for logging uncaught generic exceptions.<\/p>\n\n\n\n<p>To tackle this, we\u2019ll be covering the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Basic error handling in FastAPI<\/li><li>Why exception handlers are not great when dealing with generic exceptions<\/li><li>How to intercept generic exceptions using middleware<\/li><\/ul>\n\n\n\n<p>You\u2019ll find the source code for this article below! (Explanation in Japanese)<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/atusy\/fastapi-error-handling-demo\">https:\/\/github.com\/atusy\/fastapi-error-handling-demo<\/a><\/p>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_75 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/sreake.com\/en\/blog\/fastapi-error-handling-basics-2\/#Typical_Error_Handling_with_Exception_Handlers\" >Typical Error Handling with Exception Handlers<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/sreake.com\/en\/blog\/fastapi-error-handling-basics-2\/#Handling_Base_Exceptions_with_Middleware\" >Handling Base Exceptions with Middleware<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/sreake.com\/en\/blog\/fastapi-error-handling-basics-2\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Typical_Error_Handling_with_Exception_Handlers\"><\/span>Typical Error Handling with Exception Handlers<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>We\u2019ll briefly explain how to add an exception handler to your application. You can find more details in the official documentation.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote has-gray-100-background-color is-layout-flow wp-block-quote-is-layout-flow\"><p>FastAPI &gt; Learn &gt; Tutorial &#8211; User Guide &gt; Handling Errors<\/p><p><a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/handling-errors\/\">https:\/\/fastapi.tiangolo.com\/tutorial\/handling-errors\/<\/a><\/p><\/blockquote>\n\n\n\n<p>The documentation covers two ways of handling exceptions:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Return an HTTP response with errors to the client by raising an <mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">HTTPException<\/mark><\/li><li>Add a custom exception handler with <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">@app.exception_handler(...)<\/mark><\/code> to convert specified exceptions to HTTP responses<ul><li>The <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">HTTPException<\/mark><\/code> is itself converted by FastAPI\u2019s default exception handlers, so you could argue exception handlers are the only included way of catching errors!<\/li><\/ul><\/li><\/ul>\n\n\n\n<p>Custom exception handlers are useful when you want to <a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/handling-errors\/#override-the-default-exception-handlers\">override the default exception handlers<\/a>, or you need to handle exceptions provided by a third-party dependency. Here\u2019s an example of how to do that with the Google Cloud SDK, where we output error logs and return a custom response when services are unavailable:<\/p>\n\n\n\n<pre class=\"wp-block-code code-block\"><code>from fastapi import FastAPI, Request, Response\nfrom fastapi.responses import JSONResponse\nfrom google.api_core.exceptions import GoogleAPIError, ServiceUnavailable\n\n@app.exception_handler(GoogleAPIError)\nasync def handle_http_exception(__: Request, exc: GoogleAPIError) -&gt; Response:\n    if isinstance(exc, ServiceUnavailable):\n        logger.exception(\"Google Cloud is unavailable\")\n        return JSONResponse(\n            content={\"message\": \"Service Unavailable\"},\n            status_code=503,\n        )\n    raise exc # Unhandled errors are processed by FastAPI with a 500 status code\n\n<\/code><\/pre>\n\n\n\n<p>Exceptions handled by a custom exception handler are suppressed. However, this is not the case for the base <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">Exception<\/mark><\/code> class.<\/p>\n\n\n\n<p>Even if you convert generic exceptions to \u201cInternal Server Error\u201d responses, the exception is still raised. This means that while the server itself continues to run, you get the full unprocessed traceback logged out. If you\u2019re handling server error logging yourself, you\u2019ll run into duplicate logs, which is especially frustrating if you use structured logging (unprocessed tracebacks are not structured).<\/p>\n\n\n\n<p>This behavior (raising generic exceptions) is by design and comes from <a href=\"https:\/\/www.starlette.io\/\">Starlette<\/a>, the ASGI framework powering FastAPI. You can see the intent behind this when looking at Starlette\u2019s source code:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote has-gray-100-background-color is-layout-flow wp-block-quote-is-layout-flow\"><p>(in <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">starlette.middleware.errors.ServerErrorMiddleware<\/mark><\/code> )<br># We always continue to raise the exception.<\/p><p># This allows servers to log the error, or allows test clients<\/p><p># to optionally raise the error within the test case.<\/p><p><a href=\"https:\/\/github.com\/encode\/starlette\/blob\/c8a46925366361e40b65b117473db1342895b904\/starlette\/middleware\/errors.py?plain=1#L184-L186\">https:\/\/github.com\/encode\/starlette\/blob\/c8a46925366361e40b65b117473db1342895b904\/starlette\/middleware\/errors.py?plain=1#L184-L186<\/a><\/p><\/blockquote>\n\n\n\n<p>Let\u2019s see how logging goes when using an exception handler to deal with generic exceptions. You\u2019ll find the source code for this below.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/atusy\/fastapi-error-handling-demo\">https:\/\/github.com\/atusy\/fastapi-error-handling-demo<\/a><\/p>\n\n\n\n<p>(We used Starlette v0.45.3 for this test. You may find newer versions have different behavior.)<\/p>\n\n\n\n<p>In the following code, we raise an <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">HTTPException<\/mark><\/code> on the <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">\/404<\/mark><\/code> path, and a generic <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">Exception<\/mark><\/code> on the <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">\/500<\/mark><\/code> path. We add custom exception handlers for both exceptions, so the basic assumption would be that they both get handled the same: suppress the exception and return a custom response.<\/p>\n\n\n\n<pre class=\"wp-block-code code-block\"><code>from http import HTTPStatus\nfrom logging import getLogger\n\nimport uvicorn\nfrom fastapi import FastAPI, HTTPException, Request, Response\nfrom fastapi.responses import JSONResponse\n\nlogger = getLogger(\"uvicorn\")\napp = FastAPI()\n\n@app.get(\"\/404\")\nasync def raise404(__: Request):\n    raise HTTPException(status_code=404, detail=\"Not Found\")\n\n@app.get(\"\/500\")\nasync def raise500(__: Request):\n    raise Exception(\"Unexpected Error!!\")\n\n@app.exception_handler(HTTPException)\nasync def handle_http_exception(__: Request, exc: HTTPException):\n    return JSONResponse(\n        content={\"message\": HTTPStatus(exc.status_code).phrase},\n        status_code=exc.status_code,\n    )\n\n@app.exception_handler(Exception)\nasync def handle_exception(__: Request, exc: Exception) -&gt; Response:\n    logger.exception(\n        str(exc),\n        exc_info=False,  # This should be True to track errors, but False is fine for demo purposes\n    )\n    return JSONResponse(\n        content={\"message\": \"Internal Server Error\"},\n        status_code=500,\n    )\n\nif __name__ == \"__main__\":\n    uvicorn.run(app, host=\"127.0.0.1\", port=8000)\n\n<\/code><\/pre>\n\n\n\n<p>But in practice, the base <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">Exception<\/mark><\/code> is actually raised, and an unprocessed traceback is output, as we can see in the below logs:<\/p>\n\n\n\n<pre class=\"wp-block-code code-block\"><code># Visiting \"\/404\"\nINFO:     127.0.0.1:47962 - \"GET \/404 HTTP\/1.1\" 404 Not Found\n# No traceback, HTTPException was suppressed\n\n# Visiting \"\/500\"\nERROR:    Unexpected Error!!\nINFO:     127.0.0.1:47974 - \"GET \/500 HTTP\/1.1\" 500 Internal Server Error\n# Exception was raised, and we get a traceback\nERROR:    Exception in ASGI application\nTraceback (most recent call last):\n  File \"...\/demo\/.venv\/\nlib\/python3.12\/site-packages\/uvicorn\/protocols\/http\/h11_impl.py\", line 403, in run_asgi\n    result = await app(  # type: ignore&#91;func-returns-value]\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n# A long traceback later...\n  File \"...\/demo\/main.py\", line 19, in raise500\n    raise Exception(\"Unexpected Error!!\")\nException: Unexpected Error!!\n\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Handling_Base_Exceptions_with_Middleware\"><\/span>Handling Base Exceptions with Middleware<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>We\u2019ve established that handling base exceptions with a custom exception handler is not a good idea, especially if you care about your logs. As this is a design decision in Starlette and FastAPI, it would be unproductive to try to hack around this behavior.<\/p>\n\n\n\n<p>Instead of using a custom exception handler, let\u2019s use a middleware to catch generic exceptions. If we encounter an untreated exception (not handled by any existing exception handler), we catch it in the middleware, output error logs and return a custom response.<\/p>\n\n\n\n<p>To do this, we first have to remove the previous generic exception handler:<\/p>\n\n\n\n<pre class=\"wp-block-code code-block\"><code>-@app.exception_handler(Exception)\n-async def handle_exception(__: Request, exc: Exception) -&gt; Response:\n-    logger.exception(\n-        str(exc),\n-        exc_info=False,  # This should be True to track errors, but False is fine for demo purposes\n-    )\n-    return JSONResponse(\n-        content={\"message\": \"Internal Server Error\"},\n-        status_code=500,\n-    )\n<\/code><\/pre>\n\n\n\n<p>Then we add our middleware to catch generic exceptions:<\/p>\n\n\n\n<pre class=\"wp-block-code code-block\"><code>from starlette import base\n\n@app.middleware(\"http\")\nasync def server_error_middleware(\n    request: Request, call_next: base.RequestResponseEndpoint\n) -&gt; Response:\n    try:\n        return await call_next(request)\n    except Exception:\n        logger.exception(\n            \"Unexpected Error!!!\",\n            exc_info=False,  # This should be True to track errors, but False is fine for demo purposes\n        )\n        return JSONResponse(\n            status_code=500, content={\"message\": \"Internal Server Error\"}\n        )\n<\/code><\/pre>\n\n\n\n<p>Running the same test as before, we can see error handling on the <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">\/500<\/mark><\/code> path now works just like on the <code><mark style=\"background-color:#e9edf1\" class=\"has-inline-color has-orange-color\">\/404<\/mark><\/code> path: the exception is suppressed, we get nice error logs for developers, and there is no more huge traceback to look at.<\/p>\n\n\n\n<pre class=\"wp-block-code code-block\"><code># Visiting \"\/404\"\nINFO:     127.0.0.1:36242 - \"GET \/404 HTTP\/1.1\" 404 Not Found\n\n# Visiting \"\/500\"\nERROR:    Unexpected Error!!!\nINFO:     127.0.0.1:36258 - \"GET \/500 HTTP\/1.1\" 500 Internal Server Error\n\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>FastAPI\u2019s custom exception handlers are useful when dealing with known exceptions from third-party dependencies. However, when handling generic exceptions and implementing server logs, it is far better to put that logic in a middleware.<\/p>\n\n\n\n<p>This also illustrates the importance of design details in underlying libraries, in this case Starlette. When debugging cryptic implementation bugs, it may be a good idea to look under the hood and browse the Starlette source code for clues.<\/p>\n\n\n\n<p>Happy Coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This blog post is a translation of a Japanese article posted on May 8th, 2025. Hi! I\u2019m Atsushi Yasumoto (atusy) from the Sreake team. When designing an API server, it\u2019s important to handle program errors and return client or server errors. It\u2019s also crucial to output error logs for future debugging. FastAPI, the very popular [&hellip;]<\/p>\n","protected":false},"author":45,"featured_media":11828,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_locale":"en_US","_original_post":"https:\/\/sreake.com\/?p=11040","footnotes":""},"categories":[17],"tags":[14,23],"class_list":["post-11824","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","tag-sre","tag-enginner-blog","en-US"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/posts\/11824","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/users\/45"}],"replies":[{"embeddable":true,"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/comments?post=11824"}],"version-history":[{"count":1,"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/posts\/11824\/revisions"}],"predecessor-version":[{"id":13485,"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/posts\/11824\/revisions\/13485"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/media\/11828"}],"wp:attachment":[{"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/media?parent=11824"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/categories?post=11824"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sreake.com\/wp-json\/wp\/v2\/tags?post=11824"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}