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 HTTPException inherits 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

ExceptionRaised whenImport from
RequestValidationErrorpath/query/body param fails validationfastapi.exceptions
ResponseValidationErrorresponse model validation failsfastapi.exceptions
ValidationErrormanual Pydantic model parse failspydantic
HTTPExceptionyou raise it explicitlyfastapi
StarletteHTTPExceptionrouting 404/405, or abovestarlette.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)