Error Handling
Implement proper error handling and validation in Express.js applications
40 minā¢By Priygop Teamā¢Last updated: Feb 2026
Error Handling in Express.js
Error handling is crucial for building robust Express.js applications. Express.js provides built-in error handling mechanisms and allows you to create custom error handlers for different scenarios.
Built-in Error Handling
Example
const express = require('express');
const app = express();
// Error handling middleware (must be last)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
// Async error handling
app.get('/async', async (req, res, next) => {
try {
const result = await someAsyncOperation();
res.json(result);
} catch (error) {
next(error); // Pass error to error handling middleware
}
});
// Express 5.0+ async error handling
app.get('/async-v5', async (req, res) => {
const result = await someAsyncOperation();
res.json(result);
// Errors are automatically caught and passed to error middleware
});
// Custom error class
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// Using custom error class
app.get('/users/:id', (req, res, next) => {
const userId = req.params.id;
if (!userId || isNaN(userId)) {
return next(new AppError('Invalid user ID', 400));
}
// Process user logic
res.json({ message: 'User found', id: userId });
});
// Error handling middleware for custom errors
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
} else {
// Production error response
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
} else {
// Programming or unknown errors
console.error('ERROR š„', err);
res.status(500).json({
status: 'error',
message: 'Something went wrong!'
});
}
}
});Input Validation
Example
const express = require('express');
const app = express();
app.use(express.json());
// Manual validation
app.post('/users', (req, res, next) => {
const { name, email, age } = req.body;
// Validation
if (!name || name.trim().length < 2) {
return res.status(400).json({ error: 'Name must be at least 2 characters long' });
}
if (!email || !email.includes('@')) {
return res.status(400).json({ error: 'Valid email is required' });
}
if (age && (age < 0 || age > 120)) {
return res.status(400).json({ error: 'Age must be between 0 and 120' });
}
// Process valid data
res.status(201).json({ message: 'User created successfully' });
});
// Using validation libraries
const { body, validationResult } = require('express-validator');
app.post('/users-validated', [
body('name').isLength({ min: 2 }).withMessage('Name must be at least 2 characters'),
body('email').isEmail().withMessage('Must be a valid email'),
body('age').optional().isInt({ min: 0, max: 120 }).withMessage('Age must be between 0 and 120')
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.status(201).json({ message: 'User created successfully' });
});
// Sanitization
const { body, sanitizeBody } = require('express-validator');
app.post('/users-sanitized', [
body('name').trim().escape(),
body('email').normalizeEmail(),
sanitizeBody('age').toInt()
], (req, res) => {
// Data is now sanitized
res.status(201).json({ message: 'User created', data: req.body });
});Advanced Error Handling Patterns
Example
// Advanced error handling patterns for Express.js
const express = require('express');
const app = express();
// Custom error classes
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(message, details = []) {
super(message, 400);
this.details = details;
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404);
}
}
// Error handling middleware
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log error
console.error('Error:', err);
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Resource not found';
error = new AppError(message, 404);
}
// Mongoose duplicate key
if (err.code === 11000) {
const message = 'Duplicate field value entered';
error = new AppError(message, 400);
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message).join(', ');
error = new ValidationError(message);
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
const message = 'Invalid token';
error = new AppError(message, 401);
}
if (err.name === 'TokenExpiredError') {
const message = 'Token expired';
error = new AppError(message, 401);
}
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
...(error.details && { details: error.details })
});
};
// Async error wrapper
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage examples
app.get('/users/:id', asyncHandler(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new NotFoundError('User'));
}
res.json({ success: true, data: user });
}));
app.post('/users', asyncHandler(async (req, res, next) => {
const { name, email } = req.body;
if (!name || !email) {
return next(new ValidationError('Name and email are required'));
}
const user = await User.create({ name, email });
res.status(201).json({ success: true, data: user });
}));
// Global error handler
app.use(errorHandler);
module.exports = app;Error Handling Best Practices
- Use custom error classes: Create specific error types for different scenarios
- Implement global error handler: Centralize error handling logic
- Log errors properly: Use structured logging with appropriate levels
- Don't expose sensitive information: Sanitize error messages in production
- Use appropriate HTTP status codes: Match status codes to error types
- Handle async errors: Use try-catch or async error wrappers
- Implement error monitoring: Use services like Sentry for production
- Create error recovery strategies: Implement retry logic where appropriate
Mini-Project: comprehensive Error Handling System
Example
// Complete error handling system for Express.js application
const express = require('express');
const winston = require('winston');
const Sentry = require('@sentry/node');
const app = express();
// Configure logging
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
// Initialize Sentry for error monitoring
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV || 'development'
});
// Custom error classes
class AppError extends Error {
constructor(message, statusCode, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
this.timestamp = new Date().toISOString();
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(message, field = null) {
super(message, 400);
this.field = field;
this.type = 'ValidationError';
}
}
class AuthenticationError extends AppError {
constructor(message = 'Authentication failed') {
super(message, 401);
this.type = 'AuthenticationError';
}
}
class AuthorizationError extends AppError {
constructor(message = 'Insufficient permissions') {
super(message, 403);
this.type = 'AuthorizationError';
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404);
this.type = 'NotFoundError';
}
}
class ConflictError extends AppError {
constructor(message = 'Resource conflict') {
super(message, 409);
this.type = 'ConflictError';
}
}
class RateLimitError extends AppError {
constructor(message = 'Too many requests') {
super(message, 429);
this.type = 'RateLimitError';
}
}
// Error handling utilities
const handleAsync = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
const handleValidation = (schema) => (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
const details = error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message
}));
return next(new ValidationError('Validation failed', details));
}
next();
};
// Error response formatter
const formatErrorResponse = (err, req) => {
const response = {
success: false,
error: {
message: err.message,
type: err.type || 'UnknownError',
timestamp: err.timestamp || new Date().toISOString(),
requestId: req.requestId || 'unknown'
}
};
// Add additional details in development
if (process.env.NODE_ENV === 'development') {
response.error.stack = err.stack;
response.error.details = err.details;
}
return response;
};
// Global error handler
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log error
logger.error({
message: err.message,
stack: err.stack,
requestId: req.requestId,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent')
});
// Send to Sentry
Sentry.captureException(err);
// Handle specific error types
if (err.name === 'CastError') {
error = new NotFoundError('Resource');
}
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
error = new ConflictError(`${field} already exists`);
}
if (err.name === 'ValidationError') {
const details = Object.values(err.errors).map(val => ({
field: val.path,
message: val.message
}));
error = new ValidationError('Validation failed', details);
}
if (err.name === 'JsonWebTokenError') {
error = new AuthenticationError('Invalid token');
}
if (err.name === 'TokenExpiredError') {
error = new AuthenticationError('Token expired');
}
// Send error response
const statusCode = error.statusCode || 500;
const response = formatErrorResponse(error, req);
res.status(statusCode).json(response);
};
// 404 handler
const notFoundHandler = (req, res, next) => {
const error = new NotFoundError(`Route ${req.originalUrl}`);
next(error);
};
// Rate limiting error handler
const rateLimitHandler = (req, res) => {
const error = new RateLimitError('Too many requests, please try again later');
const response = formatErrorResponse(error, req);
res.status(429).json(response);
};
// Apply middleware
app.use(Sentry.requestHandler());
app.use(express.json());
// Request ID middleware
app.use((req, res, next) => {
req.requestId = Math.random().toString(36).substr(2, 9);
res.setHeader('X-Request-ID', req.requestId);
next();
});
// Example routes with error handling
app.get('/users/:id', handleAsync(async (req, res, next) => {
const { id } = req.params;
if (!id || isNaN(id)) {
throw new ValidationError('Invalid user ID');
}
// Simulate database operation
const user = await simulateDatabaseOperation('findUser', id);
if (!user) {
throw new NotFoundError('User');
}
res.json({ success: true, data: user });
}));
app.post('/users', handleValidation(userSchema), handleAsync(async (req, res, next) => {
const { name, email } = req.body;
// Simulate database operation
const existingUser = await simulateDatabaseOperation('findUserByEmail', email);
if (existingUser) {
throw new ConflictError('User with this email already exists');
}
const user = await simulateDatabaseOperation('createUser', { name, email });
res.status(201).json({ success: true, data: user });
}));
// Simulate database operations
async function simulateDatabaseOperation(operation, data) {
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 100));
switch (operation) {
case 'findUser':
return data === '1' ? { id: 1, name: 'John Doe', email: 'john@example.com' } : null;
case 'findUserByEmail':
return data === 'john@example.com' ? { id: 1, name: 'John Doe', email: 'john@example.com' } : null;
case 'createUser':
return { id: Date.now(), ...data, createdAt: new Date() };
default:
return null;
}
}
// User validation schema (using Joi)
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150).optional()
});
// Apply error handlers
app.use(notFoundHandler);
app.use(errorHandler);
app.use(Sentry.errorHandler());
// Graceful shutdown
process.on('SIGTERM', () => {
logger.info('SIGTERM received, shutting down gracefully');
process.exit(0);
});
process.on('SIGINT', () => {
logger.info('SIGINT received, shutting down gracefully');
process.exit(0);
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
});
module.exports = app;Try It Yourself ā Express.js Framework
Try It Yourself ā Express.js FrameworkHTML
HTML Editor
ā ValidTab = 2 spaces
HTML|39 lines|1938 chars|ā Valid syntax
UTF-8