Skip to content

Error Handling

dichit-backend uses typed custom errors mapped centrally to HTTP responses. Errors are defined in src/shared/errors and mapped by the error-handler plugin (src/infrastructure/plugins/03-error-handler.ts).

Error model

flowchart LR
    E["Throw typed error (use case / service)"] --> M["error-handler plugin"]
    M -->|"UnprocessableEntityError"| 422
    M -->|"NotFoundError"| 404
    M -->|"UnauthorizedError"| 401
    M -->|"ForbiddenError"| 403
    M -->|"ConflictError"| 409
    M -->|"unexpected"| 500

Rules

  1. Reuse src/shared/errors before creating new error shapes.
  2. Throw typed errors in application/infrastructure code; let the plugin map them.
  3. Never expose stack traces or internal messages in responses.
  4. Keep error responses stable for clients (see the Swagger error schemas in src/shared/swagger-schemas/general).
  5. Do not leak whether an unauthorized record exists (see authorization.md).
  6. allowErrorHandlerOverride: true is set on Fastify so Sentry/error handler wiring composes (see monitoring).

Response shape

Typed errors serialize to a consistent shape. Example:

{
  "statusCode": 422,
  "error": "Unprocessable Entity",
  "message": "...",
  "path": "body.field"
}

Error handling in WebSocket

WS errors use their own envelope and codes (e.g. FORBIDDEN, RATE_LIMITED), defined in api/websocket/errors.md.

Logging

  • Log failures with enough context (correlation id, operation) to debug.
  • Never log secrets, OTPs, tokens, or full auth headers.
  • Unexpected errors reach Sentry automatically (see infrastructure/monitoring.md).

Best practices checklist

  • Throw the most specific typed error available
  • No console.error in use cases — use the logger with request context
  • Validate input before DB operations (see validation.md)
  • Wrap provider errors into typed errors at the infrastructure boundary
  • Add tests for the error cases (see testing.md)