Skip to main content

Hierarchy

PyqDeck uses a custom error hierarchy based on a BaseError class to ensure all errors have a consistent structure and status code.

Custom Error Classes

Located in backend/src/utils/errors/index.js:
ClassHTTP CodePurpose
ValidationError400Invalid input data or Zod validation failure.
UnauthorizedError401Missing or invalid authentication token.
ForbiddenError403Authenticated user lacks required permissions.
NotFoundError404Resource does not exist.
ConflictError409Resource already exists or state conflict.
RateLimitError429Too many requests from a single client.

Global Error Handler

The errorHandler middleware (backend/src/middlewares/errorHandler.js) is the final stop for every request.
  1. Sentry Integration: If SENTRY_DSN is configured, all 500+ errors are automatically reported to Sentry.
  2. Logging: All errors are logged locally via the loggerService.
  3. Formatting: Errors are transformed into a standard JSON structure using errorFormatter.
  4. Production Safety: In production, stack traces are stripped from 500 errors to avoid leaking internal details.

Response Formatters

We use two utility objects in backend/src/utils/formatters/ to ensure the API always returns a predictable structure.

Success Response (successFormatter.js)

Used for all non-error responses.
{
  "status": "success",
  "message": "Operation successful",
  "data": { ... },
  "code": 200
}
  • formatSuccess(data, message, code): For single objects or simple operations.
  • formatList(items, total, page, limit): specifically for paginated lists, adding a pagination metadata object.

Error Response (errorFormatter.js)

{
  "status": "error",
  "message": "Resource not found",
  "code": "NOT_FOUND",
  "statusCode": 404
}

Best Practices

  • Never res.send() directly: Always use the formatters in controllers.
  • Throw, don’t return: In services and repositories, throw the appropriate BaseError. The catchAsync wrapper in controllers will automatically pass it to the global error handler.
  • Operational vs. Programmer Errors: BaseError subclasses are considered “operational” (expected). Everything else is a “programmer error” and will result in a 500 status code.