JavaScript Mastery: From Fundamentals to Modern ES2024+
HomeInsightsCoursesJavaScriptCustom Errors & Error Handling Patterns
Architectural Design

Custom Error Hierarchies

Elevate your error handling from generic strings to semantic object hierarchies. Learn to use `Error.captureStackTrace` and class inheritance to build self-documenting, resilient application logic.

Why Extend the Error Class?

In a production system, a simple `throw new Error("fail")` is rarely sufficient. Engineering teams need to distinguish between **Operational Errors** (like a 404 from an API) and **Programmer Bugs** (like a null pointer). By extending the base `Error` class, you can attach critical metadata such as HTTP status codes, internal error identifiers, and recovery hints.

JAVASCRIPT
// Professional Pattern: The Base Application Error
class AppError extends Error {
    constructor(message, statusCode = 500) {
        super(message);
        this.name = this.constructor.name;
        this.statusCode = statusCode;
        this.isOperational = true; // For distinguishing programming bugs from operational errors
        
        // Technical Insight: Capturing the Stack Trace
        // This keeps the constructor call out of the stack trace
        Error.captureStackTrace(this, this.constructor);
    }
}

class ValidationError extends AppError {
    constructor(message, fields) {
        super(message, 400);
        this.fields = fields; // Attach field-specific validation failures
    }
}

The Power of Instanceof

The primary benefit of a class-based error hierarchy is the ability to use the `instanceof` operator in your `catch` blocks. This allows you to implement specific recovery logic (e.g., retrying a network request or redirecting a user to a login page) without parsing fragile error message strings.

JAVASCRIPT
// Domain-Driven Design: Error Hierarchies
class PaymentsError extends AppError {}

class InsufficientFundsError extends PaymentsError {
    constructor(amountNeeded) {
        super(`Transaction failed: $${amountNeeded} required`, 402);
    }
}

// Usage in an orchestrator
try {
    processPayment(user, 100);
} catch (error) {
    if (error instanceof InsufficientFundsError) {
        promptUserForTopUp(error.message);
    } else if (error instanceof PaymentsError) {
        logToSecurityTeam(error);
    }
}

Technical Insight: captureStackTrace

V8 (the engine behind Chrome and Node.js) provides `Error.captureStackTrace(target, constructor)`. In a custom error class, calling this ensures that the stack trace points directly to where the error was *thrown*, rather than the line inside your custom error's `constructor`. This drastically improves "time to resolution" during debugging.

Custom Error Best Practices:

  • ✅ **Inheritance:** Always extend from a base `AppError` to share common logic across the system.
  • ✅ **Metadata:** Attach machine-readable codes (e.g., `E_AUTH_EXPIRED`) alongside human messages.
  • ✅ **Clean Stacks:** Use `captureStackTrace` to keep your debugging logs focused.
  • ✅ **Semantic Naming:** Name your classes after the *problem*, not the *solution* (e.g., `TimeoutError`).
  • ✅ **Domain Boundaries:** Group errors by module (e.g., `StorageError`, `NetworkError`).

What's Next?

Now that we can handle errors structurally, let's master the most powerful text-processing tool: Regex!