Menu

Exception Handling & Response Codes

Relevant source files

This document explains the exception handling architecture, response code standards, and error propagation mechanisms in the FastAPI Best Architecture. It covers how different exception types are caught, transformed into standardized responses, and logged through the middleware pipeline.

For information about the middleware that captures exception data, see Middleware Pipeline. For details on how exceptions are logged, see Logging & Operation Logs.

Purpose and Scope

The exception handling system provides:

  • Centralized exception catching and transformation
  • RFC-compliant HTTP status code validation
  • Standardized JSON response format with trace IDs
  • Environment-aware error detail disclosure
  • Integration with the middleware pipeline for audit logging
  • Internationalized validation error messages

All exception handlers are registered in backend/common/exception/exception_handler.py78-196 via the register_exception() function.

Exception Handler Architecture

The application registers six exception handlers during startup, each responsible for a specific exception type:

HandlerException TypeStatus CodePurpose
http_exception_handlerHTTPExceptionAs raisedStandard HTTP errors
fastapi_validation_exception_handlerRequestValidationError422FastAPI request validation
pydantic_validation_exception_handlerValidationError422Pydantic model validation
assertion_error_handlerAssertionError500Assertion failures
custom_exception_handlerBaseExceptionErrorAs definedBusiness logic exceptions
all_unknown_exception_handlerException500Catch-all for unhandled errors

Exception Handler Registration Flow

Sources: backend/core/registrar.py76-114 backend/common/exception/exception_handler.py78-196

Exception Processing Sequence

The following diagram shows how exceptions flow through the handling system and integrate with middleware:

Sources: backend/common/exception/exception_handler.py78-196 backend/middleware/opera_log_middleware.py55-66

HTTP Status Code Validation

All exception handlers use _get_exception_code() to validate HTTP status codes against RFC standards before returning responses. This prevents invalid status codes from causing client errors.

The function checks against Python's http.STATUS_PHRASES dictionary, which is based on the IANA HTTP Status Code Registry.

Sources: backend/common/exception/exception_handler.py17-33

Response Format Standardization

All exception handlers return responses in a consistent JSON format using MsgSpecJSONResponse:

FieldTypeDescription
codeintHTTP status code or custom code
msgstrError message
dataanyAdditional error details (dev mode only)
trace_idstrRequest correlation ID for debugging

The trace ID is added to every exception response via get_request_trace_id() to enable request correlation across logs and responses.

Sources: backend/common/exception/exception_handler.py74-75 backend/utils/trace_id.py5-9

Exception Types and Handlers

HTTPException Handler

Handles standard Starlette/FastAPI HTTP exceptions raised by the framework or application code.

Handler: backend/common/exception/exception_handler.py79-103

Behavior:

  • Development mode: Returns the original exception status code and detail message
  • Production mode: Returns a generic error response using CustomResponseCode.HTTP_400
  • Stores exception info in ctx.__request_http_exception__
  • Preserves original exception headers

Example Usage:

Validation Error Handlers

Handles Pydantic and FastAPI request validation errors with internationalization support.

Handlers:

Both handlers delegate to _validation_exception_handler() backend/common/exception/exception_handler.py36-75

Behavior:

  • Returns HTTP 422 status code
  • i18n support: For non-English languages, translates error messages using t(f'pydantic.{error["type"]}')
  • Development mode: Includes full error details with field names and input values
  • Production mode: Returns only the error message without exposing input data
  • Stores exception info in ctx.__request_validation_exception__

Validation Error Structure:

Sources: backend/common/exception/exception_handler.py36-75 backend/common/exception/exception_handler.py105-125

AssertionError Handler

Handles assertion failures in the application code.

Handler: backend/common/exception/exception_handler.py127-150

Behavior:

  • Returns HTTP 500 status code
  • Development mode: Returns assertion message from exc.args
  • Production mode: Returns generic server error using CustomResponseCode.HTTP_500
  • Stores exception info in ctx.__request_assertion_error__

Example Usage:

Custom Exception Handler (BaseExceptionError)

Handles application-specific business logic exceptions that inherit from BaseExceptionError.

Handler: backend/common/exception/exception_handler.py152-172

Behavior:

  • Returns status code defined in the exception
  • Exposes custom error code, message, and optional data
  • Supports background tasks via exc.background
  • Stores exception info in ctx.__request_custom_exception__
  • No environment filtering: Always returns full exception details

Custom Exception Attributes:

AttributeTypeDescription
codeintHTTP status code
msgstrError message
dataanyOptional additional data
backgroundBackgroundTaskOptional background task

Sources: backend/common/exception/exception_handler.py152-172

Unknown Exception Handler

Catch-all handler for any unhandled exceptions.

Handler: backend/common/exception/exception_handler.py174-196

Behavior:

  • Returns HTTP 500 status code
  • Development mode: Returns exception message via str(exc)
  • Production mode: Returns generic server error using CustomResponseCode.HTTP_500
  • Does not store in ctx (no middleware integration)

Sources: backend/common/exception/exception_handler.py174-196

Environment-Based Error Disclosure

The application uses the ENVIRONMENT setting to control error detail exposure:

Exception TypeDevelopment ModeProduction Mode
HTTPExceptionFull status code and detailGeneric error message
ValidationErrorFull error list with inputsError message only
AssertionErrorAssertion messageGeneric error message
BaseExceptionErrorFull exception detailsFull exception details
Unknown ExceptionException stringGeneric error message

Configuration: backend/core/conf.py22

Security rationale: Production mode hides implementation details that could be exploited by attackers, such as:

  • Database field names and validation rules
  • Internal file paths
  • Stack traces
  • Input data that failed validation

Sources: backend/core/conf.py22 backend/common/exception/exception_handler.py88-96 backend/common/exception/exception_handler.py136-144

Exception Propagation to Middleware

Exception handlers store exception information in the ctx context object using specific marker attributes. The OperaLogMiddleware retrieves this information to populate operation logs with error details.

Context Storage Markers

Middleware Integration Code

The OperaLogMiddleware checks for exceptions after the response is generated:

backend/middleware/opera_log_middleware.py55-66

The extracted code and msg are used to populate the operation log entry backend/middleware/opera_log_middleware.py94-115

Sources: backend/middleware/opera_log_middleware.py55-73 backend/common/exception/exception_handler.py73 backend/common/exception/exception_handler.py97 backend/common/exception/exception_handler.py145 backend/common/exception/exception_handler.py166

Response Code Standards

The application distinguishes between two types of response codes:

StandardResponseCode

HTTP status codes that conform to RFC standards and the IANA registry. These are validated by _get_exception_code() before being returned to clients.

Common codes used:

  • HTTP_400: Bad Request
  • HTTP_422: Unprocessable Entity (validation errors)
  • HTTP_500: Internal Server Error

CustomResponseCode

Application-specific codes for business logic errors. These extend the standard codes with custom semantics while still using valid HTTP status codes as the transport mechanism.

Referenced in: backend/common/exception/exception_handler.py10 backend/common/response/response_code.py (file not provided)

Sources: backend/common/exception/exception_handler.py10 backend/common/exception/exception_handler.py17-33

Trace ID Integration

Every exception response includes a trace_id field obtained via get_request_trace_id(), which retrieves the trace ID from the request context:

backend/utils/trace_id.py5-9

The trace ID is set by the ContextMiddleware (via RequestIdPlugin) and propagated through:

  1. The ctx context object
  2. Log messages (see Logging & Operation Logs)
  3. Exception responses
  4. HTTP response headers (X-Request-ID)

This enables end-to-end request correlation for debugging and monitoring.

Sources: backend/utils/trace_id.py5-9 backend/common/exception/exception_handler.py14 backend/common/exception/exception_handler.py74 backend/core/registrar.py167-174

Exception Handling Best Practices

When to Use Each Exception Type

  1. HTTPException: Standard HTTP errors (404, 403, etc.)

  2. BaseExceptionError: Business logic errors with custom codes

  3. AssertionError: Precondition checks in development

  4. Let Pydantic validate: For request validation, rely on Pydantic models rather than manual checks

Exception Context Integration

All custom exceptions should be caught before reaching the middleware to ensure proper ctx storage. The exception handlers automatically integrate with the middleware pipeline via the ctx markers.

Sources: backend/common/exception/exception_handler.py78-196 backend/middleware/opera_log_middleware.py55-73