Understanding Errors in Express Applications
Error handling is one of the most critical aspects of creating robust web applications. In production environments, proper error handling can mean the difference between a minor hiccup and a complete service outage.
Why Error Handling Matters
- User Experience - Friendly error messages improve usability
- Developer Experience - Detailed error information aids debugging
- Application Stability - Prevents crashes due to unhandled errors
- Security - Avoids leaking sensitive information in error responses
- Business Impact - Reduces downtime and operational costs
Think of error handling as a safety net for your application. Just as a circus performer needs a safety net to catch them if they fall, your code needs error handling to prevent it from crashing when unexpected situations occur.
Express Error Handling Fundamentals
Express provides a built-in error handling system, centered around special middleware functions that take four parameters instead of the usual three.
Basic Express Error Handler
const express = require('express');
const app = express();
// Regular middleware and routes
app.get('/api/items', (req, res) => {
// Route handler code...
});
// Error handling middleware (note the four parameters)
app.use((err, req, res, next) => {
console.error(err.stack);
// Send error response
res.status(500).json({
error: {
message: 'Something went wrong!'
}
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Key Points About Error Middleware
- Error middleware must have exactly four parameters:
(err, req, res, next) - Express identifies error-handling middleware by the number of parameters
- Error handlers should be defined after all other middleware and routes
- Multiple error handlers can be chained to handle different types of errors
- The
next()function can be used to pass control to the next error handler
Error Types and Classification
To handle errors effectively, it's important to understand the different types of errors that can occur in your application.
Operational vs. Programmer Errors
A useful way to classify errors is to divide them into operational errors and programmer errors:
- Operational Errors - Expected problems that may occur during normal operation (e.g., network failures, invalid user input)
- Programmer Errors - Bugs in the code that should be fixed rather than handled at runtime (e.g., trying to access properties of undefined)
Common Error Types in Node.js
// Operational Errors - Can be handled gracefully
try {
// Database connection failure
await db.connect();
} catch (err) {
if (err.code === 'ECONNREFUSED') {
console.error('Database connection failed, retrying...');
// Retry logic...
}
}
// Validation error - Expected in normal operation
if (!isValidEmail(email)) {
const validationError = new Error('Invalid email format');
validationError.statusCode = 400;
validationError.type = 'VALIDATION_ERROR';
throw validationError;
}
// Programmer Errors - Should be fixed, not handled
function processData(data) {
// Trying to access property of undefined (programmer error)
return data.items.map(item => item.name); // Will crash if data or data.items is undefined
}
// Better approach to prevent programmer error
function processData(data) {
// Check assumptions before proceeding
if (!data || !data.items || !Array.isArray(data.items)) {
throw new TypeError('Invalid data format');
}
return data.items.map(item => item.name);
}
JavaScript Built-in Error Types
JavaScript provides several built-in error types that provide useful information about what went wrong:
- Error - Base error type
- SyntaxError - Invalid JavaScript syntax
- TypeError - Value is not of the expected type
- ReferenceError - Reference to an undefined variable
- RangeError - Value outside of valid range
- URIError - Invalid URI encoding/decoding
- EvalError - Error with eval() function
Creating Custom Error Classes
For more advanced error handling, you'll want to create custom error classes that extend the base Error class. This allows you to add additional properties and methods to your errors.
Creating Custom Error Classes
// Base application error class
class AppError extends Error {
constructor(message, statusCode = 500, type = 'SERVER_ERROR') {
super(message);
this.statusCode = statusCode;
this.type = type;
this.isOperational = true; // Flag for operational vs programmer errors
// Capture stack trace, excluding the constructor call from it
Error.captureStackTrace(this, this.constructor);
}
}
// Specific error types
class ValidationError extends AppError {
constructor(message, invalidFields = []) {
super(message, 400, 'VALIDATION_ERROR');
this.invalidFields = invalidFields;
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404, 'NOT_FOUND');
this.resource = resource;
}
}
class AuthenticationError extends AppError {
constructor(message = 'Authentication failed') {
super(message, 401, 'AUTHENTICATION_ERROR');
}
}
class AuthorizationError extends AppError {
constructor(message = 'Not authorized') {
super(message, 403, 'AUTHORIZATION_ERROR');
}
}
class DatabaseError extends AppError {
constructor(message, originalError) {
super(message, 500, 'DATABASE_ERROR');
this.originalError = originalError;
}
}
// Usage examples
try {
// Validation error
const user = validateUser(req.body);
if (!user.isValid) {
throw new ValidationError('Invalid user data', user.errors);
}
// Resource not found
const product = await Product.findById(id);
if (!product) {
throw new NotFoundError('Product');
}
// Authorization error
if (!hasPermission(user, 'edit:products')) {
throw new AuthorizationError('You do not have permission to edit products');
}
} catch (err) {
next(err); // Pass to error handling middleware
}
Creating a hierarchy of error classes has several benefits:
- Enables more precise error categorization
- Makes checking error types easier with
instanceof - Allows for additional context in specific error types
- Creates self-documenting code for error scenarios
- Provides consistent error formats across the application
Synchronous Error Handling
Express automatically catches and forwards synchronous errors thrown in route handlers and middleware to your error handling middleware.
Handling Synchronous Errors
// Express automatically catches synchronous errors
app.get('/api/items/:id', (req, res) => {
// This error will be caught automatically by Express
const id = req.params.id;
if (!id.match(/^[0-9a-fA-F]{24}$/)) {
throw new ValidationError('Invalid ID format');
}
// Proceed with valid ID...
res.json({ message: 'Valid ID format' });
});
// Example of validating query parameters
app.get('/api/search', (req, res) => {
const { q, limit } = req.query;
// Validate required parameters
if (!q) {
throw new ValidationError('Search query is required');
}
// Validate numeric parameters
const parsedLimit = parseInt(limit, 10);
if (limit && (isNaN(parsedLimit) || parsedLimit < 1)) {
throw new ValidationError('Limit must be a positive number');
}
// Proceed with valid parameters...
res.json({ message: 'Valid search parameters' });
});
For synchronous code, Express's built-in error handling works well without additional configuration. Simply throw errors, and they'll be caught and passed to your error handlers.
Asynchronous Error Handling
Handling errors in asynchronous code requires special attention, as they won't be automatically caught by Express.
Three Methods for Async Error Handling
Method 1: Using try/catch with async/await
// Using try/catch with async/await
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('User');
}
res.json(user);
} catch (err) {
// Pass caught error to the error handling middleware
next(err);
}
});
Method 2: Using a wrapper function
// Create a utility wrapper for async route handlers
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Use the wrapper for async routes - no try/catch needed!
app.get('/api/products/:id', asyncHandler(async (req, res) => {
const product = await Product.findById(req.params.id);
if (!product) {
throw new NotFoundError('Product');
}
res.json(product);
}));
Method 3: Using express-async-errors package
// Install: npm install express-async-errors
// At the top of your app, import the package
const express = require('express');
require('express-async-errors'); // This patches Express to handle async errors
const app = express();
// Now async errors will be caught automatically without try/catch or wrappers
app.get('/api/orders/:id', async (req, res) => {
const order = await Order.findById(req.params.id);
if (!order) {
throw new NotFoundError('Order');
}
res.json(order);
});
Handling Promise Rejections in Node.js
Always make sure to handle unhandled promise rejections at the application level:
// Add these handlers at the top level of your application
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Promise Rejection:', reason);
// Consider crashing in development to catch these early
// In production, you might want to log and continue
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// Gracefully shutdown the server
server.close(() => {
process.exit(1);
});
// If graceful shutdown fails, force exit after 1 second
setTimeout(() => {
process.exit(1);
}, 1000);
});
Creating Comprehensive Error Handlers
Now that we understand different error types and how to catch them, let's create a comprehensive error handling system for Express.
Complete Error Handling System
// error-handlers.js
/**
* Handle 404 Not Found errors
*/
function notFoundHandler(req, res, next) {
const error = new NotFoundError(`Route not found: ${req.method} ${req.originalUrl}`);
next(error);
}
/**
* Handle validation errors from Mongoose/MongoDB
*/
function mongooseValidationHandler(err, req, res, next) {
if (err.name === 'ValidationError') {
// Extract validation errors from Mongoose
const validationErrors = Object.values(err.errors).map(error => ({
field: error.path,
message: error.message
}));
// Create our custom validation error
const validationError = new ValidationError(
'Validation failed',
validationErrors
);
return next(validationError);
}
// Not a Mongoose validation error, pass to next handler
next(err);
}
/**
* Handle MongoDB duplicate key errors
*/
function duplicateKeyHandler(err, req, res, next) {
if (err.name === 'MongoError' && err.code === 11000) {
// Extract the duplicate field from the error message
const field = Object.keys(err.keyValue)[0];
const value = err.keyValue[field];
const duplicateError = new ValidationError(
`Duplicate value: '${value}' for field '${field}'`,
[{ field, message: 'Value already exists' }]
);
return next(duplicateError);
}
next(err);
}
/**
* Handle JWT authentication errors
*/
function jwtErrorHandler(err, req, res, next) {
if (err.name === 'JsonWebTokenError') {
return next(new AuthenticationError('Invalid token'));
}
if (err.name === 'TokenExpiredError') {
return next(new AuthenticationError('Token expired'));
}
next(err);
}
/**
* Development error handler - includes stack trace and details
*/
function developmentErrorHandler(err, req, res, next) {
// Log error for debugging
console.error('ERROR STACK:', err.stack);
// Get status code from error or default to 500
const statusCode = err.statusCode || 500;
// Prepare the error response with details
const errorResponse = {
error: {
message: err.message || 'Internal Server Error',
type: err.type || 'SERVER_ERROR',
stack: err.stack,
...(err.invalidFields && { invalidFields: err.invalidFields }),
...(err.resource && { resource: err.resource })
}
};
res.status(statusCode).json(errorResponse);
}
/**
* Production error handler - hides implementation details
*/
function productionErrorHandler(err, req, res, next) {
// Log error for monitoring but don't expose details
console.error('ERROR:', {
message: err.message,
type: err.type || 'SERVER_ERROR',
statusCode: err.statusCode || 500,
isOperational: err.isOperational || false,
url: req.originalUrl,
method: req.method,
ip: req.ip,
timestamp: new Date().toISOString()
});
// Get status code from error or default to 500
const statusCode = err.statusCode || 500;
// Check if this is an operational error we expected
// and can safely show to users
const isOperationalError = err.isOperational === true;
// Prepare the error response
const errorResponse = {
error: {
message: isOperationalError
? err.message
: 'Something went wrong. Please try again later.',
type: isOperationalError
? err.type || 'SERVER_ERROR'
: 'SERVER_ERROR'
}
};
// Add validation errors if applicable
if (err.invalidFields && isOperationalError) {
errorResponse.error.invalidFields = err.invalidFields;
}
// Add resource info for not found errors
if (err.resource && isOperationalError) {
errorResponse.error.resource = err.resource;
}
// Send the appropriate response
res.status(statusCode).json(errorResponse);
}
// Choose error handler based on environment
const finalErrorHandler = process.env.NODE_ENV === 'production'
? productionErrorHandler
: developmentErrorHandler;
module.exports = {
notFoundHandler,
mongooseValidationHandler,
duplicateKeyHandler,
jwtErrorHandler,
finalErrorHandler
};
Applying Error Handlers in Express App
// app.js
const express = require('express');
const errorHandlers = require('./error-handlers');
require('express-async-errors');
const app = express();
// Body parser middleware
app.use(express.json());
// API routes
app.use('/api/users', require('./routes/users'));
app.use('/api/products', require('./routes/products'));
app.use('/api/orders', require('./routes/orders'));
// Apply error handling middleware in the correct order
// 1. Route not found handler (404)
app.use(errorHandlers.notFoundHandler);
// 2. Specific error type handlers
app.use(errorHandlers.mongooseValidationHandler);
app.use(errorHandlers.duplicateKeyHandler);
app.use(errorHandlers.jwtErrorHandler);
// 3. Final error handler (different for dev/prod)
app.use(errorHandlers.finalErrorHandler);
module.exports = app;
This comprehensive approach ensures that:
- Common error types are handled consistently
- Error responses have a consistent format
- Sensitive information isn't leaked in production
- Detailed error information is available for debugging in development
- Error handling logic is centralized and maintainable
Error Logging
Proper error logging is essential for debugging issues in production and monitoring application health.
Advanced Error Logging with Winston
// logger.js
const winston = require('winston');
const { format, transports } = winston;
// Define log format
const logFormat = format.printf(({ level, message, timestamp, ...meta }) => {
return `${timestamp} [${level.toUpperCase()}]: ${message} ${
Object.keys(meta).length ? JSON.stringify(meta) : ''
}`;
});
// Create logger instance
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.errors({ stack: true }),
logFormat
),
defaultMeta: { service: 'api-service' },
transports: [
// Write logs with level 'error' and below to error.log
new transports.File({ filename: 'logs/error.log', level: 'error' }),
// Write all logs to combined.log
new transports.File({ filename: 'logs/combined.log' })
]
});
// If we're not in production, also log to the console
if (process.env.NODE_ENV !== 'production') {
logger.add(new transports.Console({
format: format.combine(
format.colorize(),
format.simple()
)
}));
}
// Create error logging middleware
const errorLogger = (err, req, res, next) => {
// Log the error with context
logger.error(`${err.message}`, {
error: {
type: err.type || err.name,
statusCode: err.statusCode || 500,
stack: err.stack
},
request: {
url: req.originalUrl,
method: req.method,
params: req.params,
query: req.query,
body: process.env.NODE_ENV === 'development' ? req.body : '[REDACTED]',
ip: req.ip,
user: req.user ? req.user.id : 'anonymous'
}
});
next(err);
};
module.exports = {
logger,
errorLogger
};
Using the Error Logger in Express
// app.js
const express = require('express');
const { errorLogger } = require('./logger');
const errorHandlers = require('./error-handlers');
require('express-async-errors');
const app = express();
// Body parser middleware
app.use(express.json());
// Routes
app.use('/api/users', require('./routes/users'));
// ... other routes
// Error handling middleware
// 1. Log all errors first
app.use(errorLogger);
// 2. Route not found handler
app.use(errorHandlers.notFoundHandler);
// 3. Specific error handlers
app.use(errorHandlers.mongooseValidationHandler);
// ... other specific handlers
// 4. Final error handler
app.use(errorHandlers.finalErrorHandler);
module.exports = app;
Log Security Considerations
- Sensitive Data: Never log passwords, tokens, or personal information
- Log Rotation: Implement log rotation to prevent excessive disk usage
- Log Levels: Use appropriate log levels (error, warn, info, debug)
- Log Sanitization: Sanitize log data to prevent log forging attacks
- Log Storage: In production, consider sending logs to a centralized service
Error Monitoring in Production
For production applications, you'll want to implement error monitoring and alerting to detect and respond to issues quickly.
Error Monitoring Strategies
- Error Tracking Services - Tools like Sentry, Rollbar, or New Relic
- Log Analysis - ELK Stack, Graylog, or Splunk
- Real-time Alerting - PagerDuty, OpsGenie, or custom webhooks
- Performance Monitoring - Track error rates and system metrics
Integrating Sentry for Error Tracking
// sentry.js
const Sentry = require('@sentry/node');
const { ProfilingIntegration } = require('@sentry/profiling-node');
// Initialize Sentry
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
profilesSampleRate: 1.0,
integrations: [
new ProfilingIntegration(),
],
});
// Create Sentry middleware for Express
const sentryErrorHandler = Sentry.Handlers.errorHandler();
module.exports = {
Sentry,
sentryErrorHandler
};
Using Sentry in Express App
// app.js
const express = require('express');
const { Sentry, sentryErrorHandler } = require('./sentry');
const { errorLogger } = require('./logger');
const errorHandlers = require('./error-handlers');
require('express-async-errors');
const app = express();
// Initialize Sentry request handler (must be the first middleware)
app.use(Sentry.Handlers.requestHandler());
// Body parser middleware
app.use(express.json());
// API routes
app.use('/api/users', require('./routes/users'));
// ... other routes
// Error handling middleware
// 1. Log errors to local storage
app.use(errorLogger);
// 2. Send errors to Sentry
app.use(Sentry.Handlers.errorHandler());
// 3. Specific error handlers
app.use(errorHandlers.mongooseValidationHandler);
app.use(errorHandlers.duplicateKeyHandler);
app.use(errorHandlers.jwtErrorHandler);
// 4. Final error handler
app.use(errorHandlers.finalErrorHandler);
module.exports = app;
Validation Error Handling
Input validation errors are among the most common in web applications, so it's worth implementing specialized handling for them.
Using express-validator for Validation
// validation.js
const { validationResult } = require('express-validator');
const { ValidationError } = require('./errors');
// Middleware to check for validation errors
const validateRequest = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
// Format errors for consistent output
const formattedErrors = errors.array().map(error => ({
field: error.param,
message: error.msg,
value: error.value
}));
// Create and throw a ValidationError
throw new ValidationError('Validation failed', formattedErrors);
}
next();
};
module.exports = {
validateRequest
};
Applying Validation in Routes
// users-routes.js
const express = require('express');
const { body } = require('express-validator');
const { validateRequest } = require('../validation');
const usersController = require('../controllers/users');
const router = express.Router();
// Create user validation rules
const createUserValidation = [
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('Name must be between 2 and 50 characters'),
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Must be a valid email address'),
body('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters long')
.matches(/\d/)
.withMessage('Password must contain at least one number'),
body('role')
.optional()
.isIn(['user', 'admin'])
.withMessage('Role must be either user or admin'),
// Apply validation
validateRequest
];
// Create user route with validation
router.post('/', createUserValidation, usersController.createUser);
module.exports = router;
This approach provides:
- Consistent validation error responses
- Clear feedback for client applications
- Separation of validation logic from route handlers
- Input sanitization to prevent security issues
API-Specific Error Handling
For APIs, it's important to provide machine-readable error responses that follow a consistent format.
Standard API Error Response Format
Following a standard like JSON:API or RFC 7807 "Problem Details" can improve interoperability with client applications.
JSON:API Compliant Error Handler
// api-error-handler.js
function jsonApiErrorHandler(err, req, res, next) {
// Get status code from error or default to 500
const statusCode = err.statusCode || 500;
// Basic error object
const errorResponse = {
errors: [
{
status: String(statusCode),
title: err.type || 'SERVER_ERROR',
detail: err.message || 'An unexpected error occurred'
}
]
};
// Add additional error details if available
if (err.id) {
errorResponse.errors[0].id = err.id;
}
if (err.code) {
errorResponse.errors[0].code = err.code;
}
if (err.source) {
errorResponse.errors[0].source = err.source;
} else if (err.invalidFields) {
// Convert validation errors to JSON:API format
errorResponse.errors = err.invalidFields.map(field => ({
status: String(statusCode),
title: 'VALIDATION_ERROR',
detail: field.message,
source: {
pointer: `/data/attributes/${field.field}`
}
}));
}
// In development, include the stack trace
if (process.env.NODE_ENV !== 'production') {
errorResponse.errors[0].meta = {
stack: err.stack
};
}
res.status(statusCode).json(errorResponse);
}
module.exports = jsonApiErrorHandler;
Sample JSON:API Error Response
{
"errors": [
{
"status": "400",
"title": "VALIDATION_ERROR",
"detail": "Email must be a valid email address",
"source": {
"pointer": "/data/attributes/email"
}
},
{
"status": "400",
"title": "VALIDATION_ERROR",
"detail": "Password must be at least 8 characters long",
"source": {
"pointer": "/data/attributes/password"
}
}
]
}
Testing Error Handlers
Error handlers should be thoroughly tested to ensure they work as expected in various scenarios.
Unit Testing Error Handlers
// error-handlers.test.js
const { expect } = require('chai');
const sinon = require('sinon');
const {
notFoundHandler,
mongooseValidationHandler,
productionErrorHandler
} = require('../error-handlers');
const { ValidationError, NotFoundError } = require('../errors');
describe('Error Handlers', function() {
let req, res, next;
beforeEach(function() {
req = {
method: 'GET',
originalUrl: '/api/test',
ip: '127.0.0.1'
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.spy()
};
next = sinon.spy();
});
describe('notFoundHandler', function() {
it('should create a NotFoundError and pass it to next', function() {
notFoundHandler(req, res, next);
expect(next.calledOnce).to.be.true;
expect(next.firstCall.args[0]).to.be.instanceof(NotFoundError);
expect(next.firstCall.args[0].message).to.include('/api/test');
expect(next.firstCall.args[0].statusCode).to.equal(404);
});
});
describe('mongooseValidationHandler', function() {
it('should transform Mongoose validation errors', function() {
const mongooseError = {
name: 'ValidationError',
errors: {
email: {
path: 'email',
message: 'Email is invalid'
},
password: {
path: 'password',
message: 'Password is required'
}
}
};
mongooseValidationHandler(mongooseError, req, res, next);
expect(next.calledOnce).to.be.true;
const transformedError = next.firstCall.args[0];
expect(transformedError).to.be.instanceof(ValidationError);
expect(transformedError.statusCode).to.equal(400);
expect(transformedError.invalidFields).to.have.lengthOf(2);
});
it('should pass non-Mongoose errors to next', function() {
const error = new Error('Test error');
mongooseValidationHandler(error, req, res, next);
expect(next.calledOnce).to.be.true;
expect(next.firstCall.args[0]).to.equal(error);
});
});
describe('productionErrorHandler', function() {
it('should hide sensitive information in production', function() {
const error = new Error('Database connection failed: password incorrect');
error.stack = 'Error stack trace';
productionErrorHandler(error, req, res, next);
expect(res.status.calledWith(500)).to.be.true;
expect(res.json.calledOnce).to.be.true;
const response = res.json.firstCall.args[0];
expect(response.error.message).to.equal('Something went wrong. Please try again later.');
expect(response.error.stack).to.be.undefined;
});
it('should show operational error details', function() {
const error = new NotFoundError('User');
error.isOperational = true;
productionErrorHandler(error, req, res, next);
expect(res.status.calledWith(404)).to.be.true;
const response = res.json.firstCall.args[0];
expect(response.error.message).to.equal('User not found');
expect(response.error.type).to.equal('NOT_FOUND');
expect(response.error.resource).to.equal('User');
});
});
});
Integration Testing Error Handlers
// api.test.js
const request = require('supertest');
const { expect } = require('chai');
const app = require('../app');
describe('API Error Handling', function() {
describe('Route Not Found', function() {
it('should return 404 for non-existent routes', function(done) {
request(app)
.get('/api/non-existent-route')
.expect('Content-Type', /json/)
.expect(404)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.have.property('error');
expect(res.body.error.type).to.equal('NOT_FOUND');
done();
});
});
});
describe('Validation Errors', function() {
it('should return validation errors for invalid input', function(done) {
request(app)
.post('/api/users')
.send({
// Missing required fields and invalid email
name: 'A', // Too short
email: 'not-an-email'
})
.expect('Content-Type', /json/)
.expect(400)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.have.property('error');
expect(res.body.error.type).to.equal('VALIDATION_ERROR');
expect(res.body.error.invalidFields).to.be.an('array');
expect(res.body.error.invalidFields).to.have.length.greaterThan(0);
done();
});
});
});
describe('Authentication Errors', function() {
it('should return 401 for invalid authentication', function(done) {
request(app)
.get('/api/users/profile')
.set('Authorization', 'Bearer invalid-token')
.expect('Content-Type', /json/)
.expect(401)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.have.property('error');
expect(res.body.error.type).to.equal('AUTHENTICATION_ERROR');
done();
});
});
});
});
Practical Exercises
Exercise 1: Create a Basic Error Handling System
Implement error handling for a simple Express API with the following requirements:
- Create custom error classes for common error types (NotFound, Validation, Authorization)
- Implement middleware to catch synchronous errors
- Create a utility function for handling asynchronous errors
- Set up different error responses for development and production environments
- Implement basic error logging to the console
Test your implementation with routes that throw different types of errors.
Exercise 2: Advanced Error Handling System
Extend the basic error handling system with the following advanced features:
- Integrate with a structured logging library like Winston
- Implement validation error handling using express-validator
- Create handlers for database errors (using MongoDB/Mongoose as an example)
- Implement a standardized API error response format
- Add tests for your error handlers using Mocha/Chai or Jest
- Create a simple monitoring dashboard that displays error counts and types
Integrate these components into a sample application with user authentication and data validation.
Additional Resources
Documentation and Libraries
- Express.js Error Handling Documentation
- Node.js Error Handling Documentation
- express-async-errors Package
- Winston - Logging Library
- Sentry - Error Monitoring