export class ApiError extends Error {
  public statusCode: number;
  public success: boolean;
  public errors: any[];
  public isOperational: boolean;
  constructor(
    statusCode: number = 500,
    message: string = 'Internal server error',
    errors: any[] = [],
    stack: string = ''
  ) {
    super(message);
    Object.setPrototypeOf(this, new.target.prototype);

    this.statusCode = statusCode;
    this.success = false;
    this.errors = errors;
    this.isOperational = true;

    if (stack) {
      this.stack = stack;
    } else {
      Error.captureStackTrace(this, this.constructor);
    }
  }
}

🧱 Why do we even need a custom ApiError class?

When building APIs, error handling is one of the most underestimated topics.

If you don’t design it well, you’ll end up with:

That’s where ApiError comes in — it gives structure, clarity, and control over your error handling pipeline. Let’s go step by step.


🚦 The Problem with Native JS Errors

JavaScript’s built-in Error class is very basic:

const err = new Error('Something went wrong');

This only stores:

That’s it.

No statusCode, no error type, no context — nothing useful for APIs.

If you throw this from your backend, your API might return:

{ "message": "Something went wrong" }