Skip to content

Exceptions

The core.exceptions module defines a centralized exception handling strategy for the API.

It ensures that all errors returned by the backend follow a consistent, structured format, regardless of where they originate.


Purpose

The exception layer is responsible for:

  • standardizing API error responses
  • normalizing DRF and validation errors
  • preserving compatibility with existing DRF behavior
  • simplifying frontend error handling

Entry Point

The main function:

api_exception_handler(exc, context)

This function is registered in Django settings:

REST_FRAMEWORK = { "EXCEPTION_HANDLER": "core.exceptions.api_exception_handler" }


Response Format

All errors are wrapped in a consistent structure:

{ "error": { "status_code": 400, "detail": "Validation error", "fields": { "field_name": ["Error message"] } }, "detail": "Validation error", "field_name": ["Error message"] }


Structure Explained

error (object)

Contains the structured error metadata:

  • status_code → HTTP status code
  • detail → human-readable message
  • fields → field-level validation errors (if any)

detail (top-level)

  • duplicated for DRF compatibility
  • allows tests and clients to use standard DRF patterns

field errors (top-level)

  • flattened into the root response
  • enables simple frontend usage

Example:

{ "detail": "Validation error", "email": ["This field is required."] }


Behavior

Case 1 — Standard DRF error

Input:

{ "email": ["This field is required."] }

Output:

  • detail = "Validation error"
  • fields populated

Case 2 — DRF error with detail

Input:

{ "detail": "Not found" }

Output:

  • detail preserved
  • fields empty

Case 3 — Mixed errors

Input:

{ "detail": "Invalid input", "email": ["Invalid email"] }

Output:

  • detail extracted
  • fields separated

Case 4 — Non-dict error

Input:

"Something went wrong"

Output:

  • treated as detail string

Design Decisions

1. Keep DRF compatibility

  • detail remains available at top-level
  • tests and existing clients continue to work

2. Add structured envelope

  • error object enables:
  • better frontend parsing
  • logging consistency
  • future extensibility

3. Flatten field errors

  • avoids deep nesting in frontend
  • simplifies form handling

Responsibilities

Exception Handler

  • formats response
  • extracts detail and field errors
  • builds consistent payload

Views / Services

  • raise exceptions
  • do not format responses

What Should Raise Exceptions?

Services

  • domain errors
  • validation failures
  • permission issues

Example:

raise MembershipValidationError("Invalid role")


DRF

  • serializer validation
  • permission checks
  • not found errors

What Should NOT Happen

Avoid:

  • returning Response(...) inside services
  • formatting errors in views
  • inconsistent error shapes

Benefits

This approach provides:

  • consistent API responses
  • easier frontend integration
  • better debugging and logging
  • centralized error handling

Example

Input (serializer error)

{ "email": ["This field is required."] }

Output

{ "error": { "status_code": 400, "detail": "Validation error", "fields": { "email": ["This field is required."] } }, "detail": "Validation error", "email": ["This field is required."] }


Future Improvements

Possible extensions:

  • error codes (e.g. "INVALID_ROLE")
  • error categories (validation, permission, system)
  • localization (multi-language errors)
  • correlation IDs for tracing

Summary

The exception layer ensures that:

  • all API errors are consistent
  • frontend integration is simple
  • DRF compatibility is preserved

It acts as the single source of truth for error formatting in the backend.