Handling errors¶
FastAPI answers HTTPException and validation errors with its default handlers, and lets you register your
own with @app.exception_handler. The binary keeps Starlette's error-handling layers in the same order, so a
handler catches the same exceptions and an unhandled one ends in the same 500.
HTTPException with a detail and headers, a custom exception with its handler, and an unhandled
exception. The handler is registered on the application:
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import JSONResponse
router = APIRouter(prefix="/errors", tags=["errors"])
ITEMS = {"foo": "The Foo Wrestlers"}
@router.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in ITEMS:
raise HTTPException(status_code=404, detail="Item not found", headers={"X-Error": "missing item"})
return {"item": ITEMS[item_id]}
class UnicornException(Exception):
def __init__(self, name: str):
self.name = name
# registered on the application: app.add_exception_handler(UnicornException, unicorn_handler)
async def unicorn_handler(request: Request, exc: UnicornException):
return JSONResponse(status_code=418, content={"message": f"Oops! {exc.name} did something."})
@router.get("/unicorns/{name}")
async def read_unicorn(name: str):
if name == "yolo":
raise UnicornException(name)
return {"unicorn": name}
@router.get("/divide")
async def divide(a: int, b: int):
return {"result": a / b} # b=0: an unhandled exception, FastAPI's plain 500
app.add_exception_handler(errors.UnicornException, errors.unicorn_handler)
Like every example on this site, it is compiled and compared with FastAPI in CI (how).
What is native¶
- Starlette's stack:
ServerErrorMiddleware(theException/500 handler, whose response bypasses user middleware; the exception is still logged) → user middleware (last added runs first) →ExceptionMiddleware(handlers by status code, then by the exception's MRO; FastAPI's defaults forHTTPExceptionandRequestValidationError) → router (404/405 raised asHTTPException, hence catchable). A route'sRequestValidationErrorcarries FastAPI'sexc.body(the decoded JSON body, its bytes, or None); on a form route it is not set (reading it is anAttributeError). @app.exception_handler(class | code),app.add_exception_handler(class | code, handler), also when they are registered by aconfigure(app)function (Middleware).HTTPException(code)withoutdetail: thehttp.HTTPStatusphrase of CPython ≥ 3.13 ("Content Too Large", "Unprocessable Content"), like Starlette on those versions.traceback.format_exception(...)/format_exception_only(exc): the exception's own line (module.Class: message), as CPython formats it for a traceback ofNone;exc.__traceback__is None (the binary has no Python frames), so frames and chained exceptions are not rendered.- FastAPI's default handlers called from an application's own:
await http_exception_handler(request, exc)andawait request_validation_exception_handler(request, exc)(fastapi.exception_handlers).
Validation errors (RequestValidationError, ValidationError.errors()) are described in
Models; exception classes of the project (class attributes, methods, super().__init__ of
HTTPException) in Python semantics. What the body of a 500 contains is in
Security § Errors and logs.
What stays in Python¶
Nothing specific.