// Custom error class
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
// Route that throws error
app.get('/error', (req, res, next) => {
next(new AppError('Something went wrong', 500));
});
// Async error wrapper
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// Usage with async routes
app.get('/users', asyncHandler(async (req, res) => {
const users = await User.find(); // If this throws, error handler catches it
res.json(users);
}));
// Error handling middleware -- MUST have exactly 4 parameters (err, req, res, next).
// Express identifies error handlers by their arity (parameter count).
// If you accidentally omit one parameter, Express treats it as normal middleware
// and your errors will not be caught. This is a subtle but common bug.
// This middleware MUST be registered AFTER all routes.
app.use((err, req, res, next) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
const message = err.isOperational ? err.message : 'Internal Server Error';
res.status(statusCode).json({
success: false,
error: message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});