Custom Error Classes

Extending Error

Custom error classes carry extra data (an HTTP status, an error code) and can be told apart with instanceof. Set name so logs show the right class.

Example

class AppError extends Error {
  constructor(public readonly code: string, message: string, public readonly status = 500) {
    super(message);
    this.name = new.target.name;
  }
}

class NotFoundError extends AppError {
  constructor(entity: string, id: string | number) {
    super("NOT_FOUND", `${entity} ${id} not found`, 404);
  }
}

class ValidationError extends AppError {
  constructor(public readonly fields: Record<string, string>) {
    super("VALIDATION", "Invalid input", 400);
  }
}

const errors: Error[] = [new NotFoundError("Product", 42), new ValidationError({ price: "must be > 0" }), new Error("boom")];

for (const e of errors) {
  if (e instanceof ValidationError) console.log(e.status, e.name, e.fields);
  else if (e instanceof AppError) console.log(e.status, e.name, e.message);
  else console.log(500, e.name, e.message);
}

Output

404 NotFoundError Product 42 not found
400 ValidationError { price: 'must be > 0' }
500 Error boom

Error cause

Example

ES2022 added cause to wrap a low-level error inside a meaningful one without losing the original.

function readConfig(text: string): { port: number } {
  try {
    return JSON.parse(text);
  } catch (e) {
    throw new Error("Config file is invalid", { cause: e });
  }
}

try {
  readConfig("{port: 3000");
} catch (e) {
  if (e instanceof Error) {
    console.log(e.message);
    console.log("cause:", e.cause instanceof SyntaxError ? "SyntaxError" : e.cause);
  }
}

Output

Config file is invalid
cause: SyntaxError