Node.js Error Handling — Complete Guide to Errors, Exceptions, and Robust Code
In this tutorial, you will learn about Node.js Error Handling. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js error handling is critical for building reliable applications. Proper error management prevents crashes and provides meaningful feedback when things go wrong.
What You'll Learn
By the end of this tutorial, you'll use try/catch, create custom error classes, handle async errors, manage uncaught exceptions, unhandled rejections, and implement production error patterns.
Why Error Handling Matters
Unhandled errors crash processes. In production, a single uncaught error can bring down your entire server. Proper error handling keeps your application running and helps debug issues effectively.
Real-World Use
An Express.js API middleware catches errors, logs them with context, returns user-friendly error responses, and prevents the server from crashing when a database connection fails.
Error Handling Learning Path
flowchart LR
A[Worker Threads] --> B[Error Handling]
B --> C[Debugging]
C --> D[Testing]
D --> E[Express.js]
A --> F{You Are Here}
style F fill:#f90,color:#fff
Basic Try/Catch
try {
const data = JSON.parse("invalid json");
} catch (error) {
console.error("Parse failed:", error.message);
}
Custom Error Classes
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.name = "AppError";
this.statusCode = statusCode;
this.timestamp = new Date().toISOString();
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} not found`, 404);
this.name = "NotFoundError";
}
}
try {
throw new NotFoundError("User");
} catch (err) {
console.log(`${err.name}: ${err.message} (${err.statusCode})`);
}
Async Error Handling
Async functions return promises. Unhandled promise rejections terminate Node.js.
import fs from "node:fs/promises";
async function readConfig() {
try {
const data = await fs.readFile("config.json", "utf8");
return JSON.parse(data);
} catch (err) {
if (err.code === "ENOENT") {
return { port: 3000 }; // Default config
}
throw new AppError(`Config read failed: ${err.message}`);
}
}
Express Error Middleware
import express from "express";
const app = express();
app.get("/users/:id", async (req, res, next) => {
try {
const user = await findUser(req.params.id);
if (!user) throw new NotFoundError("User");
res.json(user);
} catch (err) {
next(err); // Forward to error handler
}
});
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
error: err.message || "Internal Server Error",
statusCode,
...(process.env.NODE_ENV === "development" && { stack: err.stack })
});
});
Global Error Handlers
process.on("uncaughtException", (err) => {
console.error("Uncaught exception:", err);
// Perform cleanup, then exit
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
console.error("Unhandled rejection:", reason);
});
Common Mistakes
1. Catching Errors and Doing Nothing
An empty catch block hides errors. Always at least log the error.
2. Using throw in Callbacks Without Try/Catch
Throw inside asynchronous callbacks is not caught by outer try/catch. Use error-first callbacks or promises.
3. Not Differentiating Error Types
Using generic Error for everything makes debugging hard. Create custom error classes with meaningful names.
4. Exposing Internal Error Details in Production
Stack traces and internal error messages leak implementation details. Always sanitize production error responses.
5. Forgetting to Handle Promise Rejections
Unhandled rejections crash Node.js. Always add .catch() or use try/catch with async/await.
Practice Questions
1. What is the difference between an exception and a rejection?
An exception is thrown synchronously (try/catch). A rejection occurs when a promise is rejected (catch handler).
2. How do you create a custom error class in Node.js?
Extend the Error class. Set this.name and add custom properties. The Prototype chain helps identify error types.
3. What happens when an unhandledRejection occurs in Node.js 15+?
Node.js treats unhandled rejections as fatal and exits the Process with a non-zero exit code.
4. Why should Express error middleware have 4 parameters?
Express identifies error-handling middleware by its four parameters (err, req, res, next). Without all four, it's treated as regular middleware.
5. Challenge: Create a safe JSON parser that returns a default value on error instead of crashing.
function safeJSONParse(str, defaultVal = {}) {
try {
return JSON.parse(str);
} catch {
return defaultVal;
}
}
console.log(safeJSONParse('{"valid": true}')); // { valid: true }
console.log(safeJSONParse("invalid")); // {}
FAQ
Mini Project: Error Logging Middleware
Build an Express middleware that catches errors, logs them with context, and returns structured responses.
import express from "express";
const app = express();
const errorLogger = (err, req, res, next) => {
const logEntry = {
timestamp: new Date().toISOString(),
method: req.method,
url: req.url,
error: err.message,
stack: err.stack
};
console.error(JSON.stringify(logEntry));
next(err);
};
const errorResponder = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
success: false,
error: statusCode === 500 ? "Internal Server Error" : err.message
});
};
app.use(errorLogger);
app.use(errorResponder);
What's Next
Node.js Debugging Node.js Testing Express.js Middleware
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro