Global Error Handling Middleware & Custom AppError Classes0%
Global Error Handling Middleware & Custom AppError Classes
Beginner12 min read•Updated: Jul 11, 2026
Global Error Handling Middleware & Custom AppError Classes
Uncaught exceptions and unhandled promise rejections can crash a Node.js production server, terminating all active client connections. Building a resilient backend requires a centralized global error-handling architecture with custom operational error classes.
1. Operational Errors vs Programmer Bugs
- Operational Errors: Predictable, unavoidable runtime errors (e.g., invalid user input, invalid login password, database duplicate key, file not found). These must be caught and returned as structured HTTP client errors (
4xx). - Programmer Bugs: Unintentional bugs in code (e.g., calling
.map()onundefined, syntax errors, reading missing properties). These must be logged with full stack traces and returned as500 Internal Server Error.
2. Building a Custom AppError Class
JavaScript
// src/utils/AppError.js
export class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true; // Flag identifying predictable operational errors
Error.captureStackTrace(this, this.constructor);
}
}
3. The catchAsync Wrapper Utility
Instead of polluting every controller with repetitive try/catch boilerplate, use an asynchronous higher-order function wrapper:
JavaScript
// src/utils/catchAsync.js
export const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next); // Catches rejected promises and forwards to next(err)
};
};
// Usage in controllers:
export const getProfile = catchAsync(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new AppError('User not found with that ID', 404));
}
res.status(200).json({ status: 'success', data: user });
});
4. Centralized Global Error Handling Middleware
Place this middleware at the very end of all route definitions:
JavaScript
// src/middleware/errorHandler.js
export function errorHandler(err, req, res, next) {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
// Development: Return rich debugging info and stack trace
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
} else {
// Production: Leak zero implementation details for unhandled bugs
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
} else {
console.error('CRITICAL UNHANDLED ERROR 💥:', err);
res.status(500).json({
status: 'error',
message: 'Something went wrong on our end. Please try again later.'
});
}
}
}
Interactive Quiz
Multiple Choice Questions
Question 1
Why must the global error-handling middleware be declared with exactly four parameters
(err, req, res, next)?Question 2
What is the danger of returning raw
err.stack traces to clients in production environments?Question 3
What does the
catchAsync wrapper pattern accomplish in Express controllers?Question 4
What is an "operational error" in backend architecture?
Question 5
Where should the global error middleware
app.use(errorHandler) be mounted in the Express application?Next Lesson
Input Validation & Data Sanitization with Joi or Zod
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.