Node.js Error Handling Deep Dive — Complete Guide to Resilient Systems
In this tutorial, you will learn about Node.js Error Handling Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js error handling deep dive covers error classification, EventEmitter error propagation, global uncaughtException handlers, unhandledRejection, and building fault-tolerant Node.js systems.
What You'll Learn
By the end of this tutorial, you'll classify operational vs programmer errors, implement error boundaries, handle EventEmitter errors, configure global exception handlers, and design resilient error recovery.
Why Deep Error Handling Matters
Unhandled errors crash Node.js processes. Production systems need graceful degradation, error isolation, and recovery strategies that prevent total failure across the application.
Real-World Use
An Express API catches operational errors (DB timeout) in middleware and returns 503. Programmer errors (undefined variable) are logged to a monitoring system, and the Process restarts via PM2.
Error Handling Path
flowchart LR
A[Async Patterns] --> B[Error Handling Deep]
B --> C[Debugging]
C --> D[Profiling]
D --> E[Memory Leaks]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Operational vs Programmer Errors
Classify errors to determine the correct response: operational errors are recoverable, programmer errors indicate bugs.
class OperationalError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.name = "OperationalError";
this.statusCode = statusCode;
this.isOperational = true;
}
}
class ProgrammerError extends Error {
constructor(message) {
super(message);
this.name = "ProgrammerError";
this.isOperational = false;
}
}
function divide(a, b) {
if (b === 0) throw new OperationalError("Division by zero", 400);
if (typeof a !== "number") throw new ProgrammerError("Invalid argument type");
return a / b;
}
EventEmitter Error Handling
Always register error listeners on EventEmitters to prevent process crashes.
const { EventEmitter } = require("node:events");
const emitter = new EventEmitter();
emitter.on("error", (err) => {
console.error("Event error caught:", err.message);
});
emitter.emit("error", new OperationalError("Database connection failed", 503));
const safeEmitter = new EventEmitter();
if (safeEmitter.listenerCount("error") === 0) {
safeEmitter.on("error", (err) => {
console.error("Default error handler:", err.message);
});
}
uncaughtException and unhandledRejection
Handle globally uncaught errors and promise rejections as last resort.
process.on("uncaughtException", (err) => {
console.error("UNCAUGHT EXCEPTION:", err);
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
console.error("UNHANDLED REJECTION:", reason);
});
process.on("warning", (warning) => {
if (warning.name === "UnhandledPromiseRejectionWarning") {
console.error("Promise rejection detected:", warning.message);
}
});
Express Error Middleware
Implement centralized error handling in Express with proper status codes.
function errorMiddleware(err, req, res, next) {
if (err.isOperational) {
return res.status(err.statusCode || 500).json({
error: err.message,
code: err.code || "OPERATIONAL_ERROR",
});
}
console.error("PROGRAMMER ERROR:", err);
res.status(500).json({
error: "Internal server error",
code: "INTERNAL_ERROR",
});
}
app.use(errorMiddleware);
Async Handler Wrapper
Wrap async route handlers to catch promise rejections automatically.
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
app.get("/users/:id", asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new OperationalError("User not found", 404);
res.json(user);
}));
Common Mistakes
1. Exiting on unhandledRejection
unhandledRejection should not crash the process. Log and continue. uncaughtException should exit.
2. Overusing Try-Catch Around Every Call
Wrap logical blocks, not every line. Too many try-catch blocks hide real errors.
3. Not Differentiating Error Types
Treating all errors the same prevents appropriate responses. Use error subclasses.
4. Ignoring Error Events on Streams
Streams emit error events. Always attach error listeners to stream.pipeline.
5. Swallowing Errors in Callbacks
Callbacks that receive errors must handle or forward them. Silent failures are the hardest to debug.
Practice Questions
1. What is the difference between operational and programmer errors?
Operational errors are runtime issues (DB down, timeout). Programmer errors are bugs (undefined variable, wrong type).
2. Should you exit the process on unhandledRejection?
No. Promise rejections are recoverable. Log and continue. Only exit on uncaughtException.
3. What does asyncHandler do in Express?
Wraps async route handlers so promise rejections are forwarded to error middleware via next().
4. Why should you always listen for error events on EventEmitters?
Unhandled error events throw and crash the process. Always register error listeners.
5. Challenge: Create an error handling system with custom error classes and logging.
class AppError extends Error {
constructor(message, statusCode, code) {
super(message);
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
}
}
class NotFoundError extends AppError {
constructor(resource = "Resource") {
super(`${resource} not found`, 404, "NOT_FOUND");
}
}
function errorHandler(err, req, res, next) {
if (err.isOperational) {
return res.status(err.statusCode).json({ code: err.code, message: err.message });
}
console.error("Unexpected error:", err);
res.status(500).json({ code: "INTERNAL_ERROR", message: "Something went wrong" });
}
FAQ
Mini Project: Error Monitoring Middleware
Build Express middleware that captures and reports errors to a monitoring system.
class ErrorMonitor {
constructor() {
this.errors = [];
}
middleware() {
return (err, req, res, next) => {
this.errors.push({
message: err.message,
stack: err.stack,
path: req.path,
method: req.method,
timestamp: new Date().toISOString(),
isOperational: err.isOperational || false,
});
if (this.errors.length > 100) this.errors.shift();
next(err);
};
}
getReport() {
return {
total: this.errors.length,
operational: this.errors.filter((e) => e.isOperational).length,
programmer: this.errors.filter((e) => !e.isOperational).length,
recent: this.errors.slice(-10),
};
}
}
What's Next
Node.js Debugging Node.js Profiling Node.js PM2
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro