← Python Code FastAPI
Browse Python Concepts

Error Handling — APIRouter Exception Isolation

Mental Model

APIRouters are like self-contained mini-applications for organizing routes, but for global concerns like exception handling, the FastAPI instance acts as the ultimate authority. Exception handlers registered on a router are effective only if that router is the root, or if specific handler propagation rules are configured.

Rule: Always register custom exception handlers on the root FastAPI instance rather than sub-routers to guarantee error capturing.

The Setup

You are organizing a large service using APIRouter to isolate routes logically. You want to register a custom exception and handler to format database error outputs cleanly, declaring the handler directly on the router.

What Does This Print?

Broken code
Python
from fastapi import FastAPI, APIRouter
from fastapi.responses import JSONResponse

class DBConnectionError(Exception):
    pass

router = APIRouter()

# Registering exception handler on router level
@router.exception_handler(DBConnectionError)
async def db_error_handler(request, exc):
    return JSONResponse(status_code=503, content={"detail": "Database offline"})

@router.get("/data")
async def get_data():
    raise DBConnectionError("Lost link to primary database")

app = FastAPI()
app.include_router(router)
Predict what response the client receives when querying the /data endpoint.

The Output

What actually happens
{ "detail": "Internal Server Error" }

The server logs a standard traceback and returns a generic 500 Internal Server Error to the client instead of the configured JSON payload with a 503 status: The registered custom exception handler db_error_handler was ignored during exception dispatching.

Why Python Does This

FastAPI and Starlette manage exception handling via an ExceptionMiddleware component wrapped at the core application level. During route execution, exceptions bubble out of router path operations completely. When search matches are executed, the middleware queries exceptions registered on the central app instance. Starlette's APIRouter implements decorator methods like @router.exception_handler to preserve syntax uniformity, but it does not hook into Starlette's central exception middleware registry unless explicitly registered on the root FastAPI instance.

The Fix

Corrected pattern
Python
from fastapi import FastAPI, APIRouter
from fastapi.responses import JSONResponse

class DBConnectionError(Exception):
    pass

router = APIRouter()

@router.get("/data")
async def get_data():
    raise DBConnectionError("Lost link to primary database")

app = FastAPI()
app.include_router(router)

# Always register exception handlers directly on the primary FastAPI application object
@app.exception_handler(DBConnectionError)
async def db_error_handler(request, exc):
    return JSONResponse(status_code=503, content={"detail": "Database offline"})

Registering the db_error_handler directly on the app (FastAPI instance) ensures that it becomes a global exception handler. When DBConnectionError is raised anywhere in the application, including within routes defined in included routers, the root app instance's handler will intercept and process it, providing the desired custom response.

How This Fails in Real Systems

A payments microservice isolated credit card parsing on a payment router. During maintenance, a dynamic validation failure raised card errors that bypasses custom APIRouter handlers. Instead of clean 'Card Invalid' messages, clients received generic HTTP 500 errors, leading to payment retries.

Key Takeaway

Always register custom exception handlers on the root FastAPI instance rather than sub-routers to guarantee error capturing.
Common mistake: Expecting exception handlers registered on an APIRouter to catch exceptions raised by routes within that same router when it's included in the main FastAPI application.