Lesson 33 +10 XP

Custom AppError Class & Operational Errors

Operational vs Programming Errors

In Node.js backends, errors fall into two main categories:

  1. Operational Errors: Predictable, expected runtime errors (e.g. invalid user input, 404 Not Found, 401 Unauthorized, duplicate key).
  2. Programming Errors: Bugs in developer code (e.g. TypeError: cannot read property of undefined, syntax errors).

Creating a Custom AppError Class

class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith("4") ? "fail" : "error";
    this.isOperational = true; // Identifies known operational errors

    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = AppError;

Using AppError in Routes & Error Middleware

const AppError = require("./AppError");

app.get("/product/:id", asyncHandler(async (req, res) => {
  const product = await Product.findById(req.params.id);
  if (!product) {
    throw new AppError("No product found with that ID", 404);
  }
  res.json(product);
}));

// Global Error Handler
app.use((err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || "error";

  if (err.isOperational) {
    // Trusted operational error: send message to client
    return res.status(err.statusCode).json({
      status: err.status,
      message: err.message
    });
  }

  // Programming or unknown error: don't leak details in production!
  console.error("ERROR 💥", err);
  return res.status(500).json({
    status: "error",
    message: "Something went very wrong!"
  });
});

TL;DR

  • Create an AppError class inheriting from Error to attach HTTP status codes.
  • Mark predictable client errors as isOperational = true.
  • Hide raw stack traces for unknown programming errors in production environments.