Reusable Node Code Modules, Part 4 - Error Handling
Graceful Failures: Creating Effective Error Handling Modules
Error handling is crucial in any Node.js application, ensuring robustness and stability by catching and managing errors effectively. This post delves into reusable error handling modules, focusing on back-end APIs using Node and Express.
Common Libraries and Tools
1. express-error-handler
Description: Middleware for handling errors in Express applications
Features: Custom error responses, centralized error handling
Example:
const createError = require('http-errors');
const express = require('express');
const app = express();
const errorHandler = require('express-error-handler');
app.use((req, res, next) => {
next(createError(404));
});
app.use(errorHandler());
2. express-async-errors
Description: Handles errors in asynchronous route handlers
Features: Simplifies error handling in async/await code
Example:
require('express-async-errors');
const express = require('express');
const app = express();
app.get('/', async (req, res) => {
throw new Error('Something went wrong');
});
app.use((err, req, res, next) => {
res.status(500).send('Internal Server Error');
});
3. boom
Description: HTTP-friendly error objects
Features: Standardized error responses, easily extendable
Example:
const Boom = require('boom');
const express = require('express');
const app = express();
app.get('/', (req, res) => {
throw Boom.badRequest('Invalid query parameter');
});
app.use((err, req, res, next) => {
if (Boom.isBoom(err)) {
res.status(err.output.statusCode).json(err.output.payload);
} else {
res.status(500).send('Internal Server Error');
}
});
4. winston
Description: Logging library that supports custom error handling
Features: Flexible logging levels, transports for different outputs
Example:
const express = require('express');
const winston = require('winston');
const app = express();
const logger = winston.createLogger({
level: 'error',
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'errors.log' })
]
});
app.use((err, req, res, next) => {
logger.error(err.message);
res.status(500).send('Internal Server Error');
});
Error Handling Patterns
Synchronous Error Handling
Example:
const express = require('express');
const app = express();
app.get('/', (req, res, next) => {
try {
// Code that may throw an error
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
res.status(500).send('Internal Server Error');
});
Asynchronous Error Handling
Example:
const express = require('express');
const app = express();
app.get('/', async (req, res, next) => {
try {
// Async code that may throw an error
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
res.status(500).send('Internal Server Error');
});
Centralized Error Handling Middleware
Example:
const express = require('express');
const app = express();
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Internal Server Error');
});
Practical Examples
Custom Error Classes
Description: Creating custom error classes for better error categorization
Example:
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
}
}
const express = require('express');
const app = express();
app.get('/', (req, res, next) => {
next(new AppError('Not Found', 404));
});
app.use((err, req, res, next) => {
res.status(err.statusCode || 500).json({
status: err.status,
message: err.message
});
});
Error Handling with Promises
Example:
const express = require('express');
const app = express();
app.get('/', (req, res, next) => {
Promise.resolve().then(() => {
throw new Error('Something went wrong');
}).catch(next);
});
app.use((err, req, res, next) => {
res.status(500).send('Internal Server Error');
});
Global Error Handler
Description: A global error handler to catch all unhandled errors
Example:
const express = require('express');
const app = express();
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
error: {
message: err.message
}
});
});
process.on('unhandledRejection', (err) => {
console.error('Unhandled Rejection:', err.message);
process.exit(1);
});
Comparison of Libraries
express-error-handler
vs. express-async-errors
express-error-handler: Simple, integrates well with existing middleware, suitable for standard Express error handling
express-async-errors: Handles async/await errors seamlessly, essential for modern Node.js applications with async routes
boom
vs. Custom Error Classes
boom: Pre-defined error responses, quick to implement, good for standard HTTP errors
Custom Error Classes: Flexible, can define application-specific errors, better for complex error scenarios
winston
vs. Native Console Logging
winston: Comprehensive logging features, supports multiple transports, ideal for production applications
Native Console Logging: Simple, sufficient for small projects or debugging, lacks advanced features