← Back to FastAPI map
FastAPI · HTTP layer

Error handling

Raising HTTPException, defining your own error types, and handling anything that slips through.

errors

Overview

Errors are part of what your API promises. Raising HTTPException anywhere - in a route or a dependency - stops the request and sends back the status and message you chose. Exception handlers turn your own error types into replies, change the shape of validation errors, and give you a catch-all that logs the details but sends the caller a safe, general message.

Key concepts

HTTPException
Raise it, do not return it - raising unwinds out of nested functions and dependencies in one step.
detail
Can be a string or any JSON-serializable structure, such as an error code plus the offending field.
exception_handler
Registers a function that turns an exception type into a response.
RequestValidationError
What FastAPI raises for invalid input. Override its handler to change the shape of 422 responses.

Best practices

  • Use one error shape across the API - code, message, field - so clients can handle errors generically.
  • Raise domain exceptions such as NotFoundError in services and translate them to HTTP at the edge, keeping business code free of HTTP details.
  • Log unexpected errors with a correlation id and return that id - never the traceback.

Error handling

HTTPException - standard

Core
from fastapi import HTTPException, status @app.get("/items/{id}") async def get_item(id: int): item = db.get(id) if not item: raise HTTPException(status_code=404, detail=f"Item {id} not found") return item # With extra headers raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated", headers={"WWW-Authenticate": "Bearer"} ) # Rich detail (dict) raise HTTPException( status_code=400, detail={"code": "DUPLICATE_EMAIL", "field": "email"} )

Custom exceptions + global handlers

Custom
from fastapi import Request from fastapi.responses import JSONResponse from fastapi.exceptions import RequestValidationError import uuid, logging # Custom exception class class NotFoundError(Exception): def __init__(self, resource: str, id): self.resource, self.id = resource, id # 1. Custom exception -> handler @app.exception_handler(NotFoundError) async def not_found(req: Request, exc: NotFoundError): return JSONResponse( status_code=404, content={"error": f"{exc.resource} {exc.id} not found"}, ) # 2. Override validation error format (422) @app.exception_handler(RequestValidationError) async def validation_handler(req: Request, exc: RequestValidationError): return JSONResponse(status_code=422, content={ "errors": [{ "field": ".".join(str(l) for l in e["loc"]), "msg": e["msg"] } for e in exc.errors()] }) # 3. CATCH-ALL - never expose tracebacks to clients! @app.exception_handler(Exception) async def unhandled(req: Request, exc: Exception): error_id = str(uuid.uuid4())[:8] logging.exception(f"[{error_id}] Unhandled error") # logs the traceback return JSONResponse( status_code=500, content={"error": "Internal server error", "error_id": error_id}, )

Watch out: Pass status_code= and content= by keyword. JSONResponse's first positional parameter is content, so JSONResponse(404, {...}) sends 404 as the body and breaks.

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...