Production error handling for FastAPI — exception handlers, structured error
96
96%
Does it follow best practices?
Impact
98%
6.12xAverage score across 5 eval scenarios
Passed
No known issues
Production error handling for FastAPI applications. Covers exception handlers, structured responses, validation errors, and shutdown.
FastAPI auto-handles Pydantic validation and returns 422, but most apps need more:
{"detail": ...})# app/errors.py
from fastapi import HTTPException
class AppError(HTTPException):
def __init__(self, status_code: int, message: str, code: str = "ERROR"):
self.code = code
super().__init__(status_code=status_code, detail=message)
class NotFoundError(AppError):
def __init__(self, resource: str, id: str | None = None):
msg = f"{resource} {id} not found" if id else f"{resource} not found"
super().__init__(404, msg, "NOT_FOUND")
class ValidationError(AppError):
def __init__(self, message: str):
super().__init__(400, message, "VALIDATION_ERROR")Every FastAPI app MUST register these three handlers. Without them, errors leak internal details or use inconsistent formats.
# app/main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from app.errors import AppError
import logging
logger = logging.getLogger(__name__)
app = FastAPI()
# Handler 1: Custom app errors → structured response
@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
return JSONResponse(
status_code=exc.status_code,
content={"error": {"code": exc.code, "message": exc.detail}},
)
# Handler 2: Pydantic validation errors → field-level details
@app.exception_handler(RequestValidationError)
async def validation_error_handler(request: Request, exc: RequestValidationError):
errors = [
{"field": ".".join(str(l) for l in e["loc"][1:]), "message": e["msg"]}
for e in exc.errors()
]
return JSONResponse(
status_code=400,
content={"error": {"code": "VALIDATION_ERROR", "message": "Invalid request", "details": errors}},
)
# Handler 3: Catch-all for unexpected errors → safe generic message, log real error
@app.exception_handler(Exception)
async def generic_error_handler(request: Request, exc: Exception):
logger.exception("Unhandled error on %s %s", request.method, request.url.path)
return JSONResponse(
status_code=500,
content={"error": {"code": "INTERNAL_ERROR", "message": "An unexpected error occurred"}},
)Why all three?
from app.errors import NotFoundError, ValidationError
@app.post("/api/orders", status_code=201)
async def create_order(body: CreateOrderRequest):
if not body.items:
raise ValidationError("At least one item is required")
order = db.create_order(body)
return {"data": order}
@app.get("/api/orders/{order_id}")
async def get_order(order_id: int):
order = db.get_order(order_id)
if not order:
raise NotFoundError("Order", str(order_id))
return {"data": order}import signal
import asyncio
@app.on_event("shutdown")
async def shutdown_event():
# Close database connections, cancel background tasks
db.close()
# For uvicorn, SIGTERM handling is built in
# For custom runners:
def handle_sigterm(signum, frame):
raise SystemExit(0)
signal.signal(signal.SIGTERM, handle_sigterm)All errors return:
{
"error": {
"code": "NOT_FOUND",
"message": "Order 42 not found"
}
}Validation errors include field details:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request",
"details": [
{"field": "customer_name", "message": "field required"},
{"field": "items", "message": "ensure this value has at least 1 items"}
]
}
}raise HTTPException — replace with typed errors@app.exception_handler exists — if not, add the three handlers above