FastAPI Cheatsheet
Error Handling
Use this FastAPI reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
HTTPException — Standard Error Responses
from fastapi import FastAPI, HTTPException app = FastAPI() @app.get("/items/{id}") def get_item(id: int): item = db.get(id) if item is None: raise HTTPException( status_code=404, detail="Item not found", # included in response body headers={"X-Error": "Not Found"}, # optional extra headers ) return item
Response body:
{"detail": "Item not found"}HTTPException with Structured detail
# detail can be any JSON-serializable value: raise HTTPException( status_code=422, detail=[ {"loc": ["body", "price"], "msg": "must be positive", "type": "value_error"} ], )
Custom Exception Classes
class ItemNotFoundError(Exception): def __init__(self, item_id: int): self.item_id = item_id class PermissionDeniedError(Exception): pass
Custom Exception Handlers
from fastapi import Request from fastapi.responses import JSONResponse @app.exception_handler(ItemNotFoundError) async def item_not_found_handler(request: Request, exc: ItemNotFoundError): return JSONResponse( status_code=404, content={"error": "not_found", "item_id": exc.item_id}, ) @app.exception_handler(PermissionDeniedError) async def permission_denied_handler(request: Request, exc: PermissionDeniedError): return JSONResponse(status_code=403, content={"error": "forbidden"}) # Raise anywhere: @app.get("/items/{id}") def get_item(id: int): raise ItemNotFoundError(item_id=id)
Overriding the Default 422 Validation Error Handler
from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): return JSONResponse( status_code=422, content={ "error": "validation_failed", "detail": exc.errors(), "body": exc.body, # the raw request body that failed }, )
Overriding the Default HTTPException Handler
from fastapi.exceptions import HTTPException as FastAPIHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException @app.exception_handler(StarletteHTTPException) async def http_exception_handler(request: Request, exc: StarletteHTTPException): return JSONResponse( status_code=exc.status_code, content={"error": exc.detail}, headers=getattr(exc, "headers", None), )
FastAPI's
HTTPExceptioninherits from Starlette's. Register the Starlette one to catch all HTTP errors (including routing 404s and 405s).
Re-Using the Default Handler (add extra logic)
from fastapi.exception_handlers import ( http_exception_handler, request_validation_exception_handler, ) @app.exception_handler(StarletteHTTPException) async def custom_http_handler(request: Request, exc: StarletteHTTPException): # custom logging / alerting here logger.error(f"{exc.status_code} {request.url}") return await http_exception_handler(request, exc) # then default behavior
Global 500 Handler (catch-all)
@app.exception_handler(Exception) async def unhandled_exception_handler(request: Request, exc: Exception): logger.exception("Unhandled error", exc_info=exc) return JSONResponse( status_code=500, content={"error": "internal_server_error"}, )
Register this last — it catches everything that didn't match a more specific handler.
Exception Handler on a Router
FastAPI doesn't support exception_handler on APIRouter directly — register handlers on the app object and they apply globally.
RequestValidationError vs ValidationError
| Exception | Raised when | Import from |
|---|---|---|
RequestValidationError | path/query/body param fails validation | fastapi.exceptions |
ResponseValidationError | response model validation fails | fastapi.exceptions |
ValidationError | manual Pydantic model parse fails | pydantic |
HTTPException | you raise it explicitly | fastapi |
StarletteHTTPException | routing 404/405, or above | starlette.exceptions |
Raising HTTPException Inside a Dependency
from fastapi import Depends, HTTPException def require_auth(token: str = Depends(oauth2_scheme)): user = verify_token(token) if not user: raise HTTPException(status_code=401, detail="Unauthorized") return user # The exception propagates through Depends() and is caught by handlers
Logging Errors
import logging logger = logging.getLogger(__name__) @app.exception_handler(Exception) async def log_all_errors(request: Request, exc: Exception): logger.error( "Unhandled exception", extra={"path": str(request.url), "method": request.method}, exc_info=exc, ) return JSONResponse(status_code=500, content={"detail": "Internal server error"})
Common Error Patterns
# 400 — bad request (business logic failure) raise HTTPException(400, "Username already taken") # 401 — unauthenticated raise HTTPException(401, "Not authenticated", headers={"WWW-Authenticate": "Bearer"}) # 403 — authenticated but not allowed raise HTTPException(403, "Not enough permissions") # 404 — resource not found raise HTTPException(404, f"Item {item_id} not found") # 409 — conflict (e.g. duplicate) raise HTTPException(409, "Resource already exists") # 410 — gone (permanently deleted) raise HTTPException(410, "Resource has been deleted") # 429 — rate limited raise HTTPException(429, "Too many requests", headers={"Retry-After": "60"})
Exception Handler Registration Order
FastAPI matches handlers in most-specific first order. Register broad handlers (like Exception) after specific ones:
app.exception_handler(ItemNotFoundError)(item_not_found_handler) # specific app.exception_handler(RequestValidationError)(validation_handler) # specific app.exception_handler(StarletteHTTPException)(http_handler) # broad app.exception_handler(Exception)(unhandled_handler) # catch-all
Testing Error Responses
from fastapi.testclient import TestClient client = TestClient(app) def test_not_found(): resp = client.get("/items/99999") assert resp.status_code == 404 assert resp.json() == {"detail": "Item not found"} def test_validation_error(): resp = client.post("/items", json={"price": -1}) assert resp.status_code == 422 errors = resp.json()["detail"] assert any(e["loc"][-1] == "price" for e in errors)