When building APIs, errors will happen:

A junior backend returns random messages.

A senior backend returns consistent, structured errors across the entire system.


✅ Goals of Proper Error Handling

Goal Meaning
Do NOT expose internal details No SQL errors in response
Have 1 single error format Consistent across all endpoints
Log errors centrally So we can debug incidents
Support custom error types Validation errors, business rule errors
Gracefully fail transactions No corrupted data

✅ STEP 1 — Create Centralized Error Response Shape

All errors returned to frontend should match this:

{
  "success": false,
  "message": "Human friendly readable message",
  "code": "SOME_ERROR_CODE" // optional, good for apps
}


✅ STEP 2 — Create Custom Error Classes

src/utils/AppError.js

export default class AppError extends Error {
  constructor(message, statusCode = 400, code = "APP_ERROR") {
    super(message);
    this.statusCode = statusCode;
    this.code = code;
    Error.captureStackTrace(this, this.constructor);
  }
}

This allows throwing errors like:

throw new AppError("Invalid email format", 400, "INVALID_EMAIL");