Claude Code Express Middleware Error Handling Patterns Guide
Building reliable Express applications requires solid error handling strategies Whether you’re building APIs or full-stack applications with Claude Code, understanding middleware error handling patterns prevents unexpected crashes and provides meaningful feedback to users.
This guide covers practical Express middleware error handling patterns that work cleanly with Claude Code workflows. You’ll find code examples compatible with Express 4.x and 5.x, plus tips on integrating these patterns into your development process.
The Basics: Express Error Handling Middleware
Express provides a special middleware type specifically for error handling. Unlike regular middleware that takes three parameters (req, res, next), error handling middleware receives four: err, req, res, next.
// Basic error handling middleware
app.use((err, req, res, next) => {
console.error('Error occurred:', err.message);
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
res.status(statusCode).json({
error: {
message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
});
This pattern catches errors from anywhere in your middleware chain. Place it last in your middleware stack to ensure it catches all unhandled errors.
Creating Custom Error Classes
Rather than throwing generic Error objects, create custom error classes that carry meaningful metadata. This pattern improves debugging and allows granular error handling.
// errors/AppError.js
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) {
super(message, 400);
this.name = 'ValidationError';
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} not found`, 404);
this.name = 'NotFoundError';
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Authentication required') {
super(message, 401);
this.name = 'UnauthorizedError';
}
}
module.exports = { AppError, ValidationError, NotFoundError, UnauthorizedError };
Using these custom errors in your routes provides clear, consistent error handling:
const { ValidationError, NotFoundError } = require('./errors/AppError');
app.get('/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 (error) {
next(error);
}
});
Async Error Handling Wrapper
Since Express doesn’t automatically catch errors from async route handlers, you’ll encounter unhandled promise rejections. Create a wrapper function to handle this automatically:
// utils/asyncHandler.js
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage with route handlers
app.get('/api/products', asyncHandler(async (req, res) => {
const products = await Product.find();
res.json(products);
}));
For TypeScript projects, here’s a typed version:
import { Request, Response, NextFunction, RequestHandler } from 'express';
const asyncHandler = (
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>
): RequestHandler => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
Structured Error Response Format
Consistent error responses across your API improve client integration. Define a standard format:
// middleware/errorResponse.js
const errorResponse = (err, req, res, next) => {
const response = {
success: false,
error: {
type: err.name || 'Error',
message: err.message,
...(err.errors && { validationErrors: err.errors })
}
};
if (process.env.NODE_ENV === 'development') {
response.error.stack = err.stack;
response.error.details = err;
}
res.status(err.statusCode || 500).json(response);
};
module.exports = errorResponse;
Middleware Chaining with Error-First Callbacks
When integrating multiple middleware pieces, handle errors through proper chaining. This pattern works well with authentication and validation middleware:
// Authentication middleware that passes errors to next()
const authenticate = async (req, res, next) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return next(new UnauthorizedError('No token provided'));
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
next(new UnauthorizedError('Invalid token'));
}
};
// Protected route using middleware chain
app.post('/api/orders',
validateOrderInput,
authenticate,
asyncHandler(async (req, res) => {
const order = await Order.create({
...req.body,
userId: req.user.id
});
res.status(201).json(order);
})
);
Graceful Shutdown and Error Logging
Production applications need proper error logging and graceful shutdown handling:
// middleware/errorLogger.js
const errorLogger = (err, req, res, next) => {
const logData = {
timestamp: new Date().toISOString(),
method: req.method,
url: req.url,
body: req.body,
error: {
name: err.name,
message: err.message,
stack: err.stack
},
user: req.user?.id || 'anonymous'
};
if (err.statusCode >= 500) {
console.error('Server Error:', JSON.stringify(logData, null, 2));
// Send to logging service (Sentry, Datadog, etc.)
} else {
console.warn('Client Error:', JSON.stringify(logData, null, 2));
}
next(err);
};
module.exports = errorLogger;
For graceful shutdown, ensure your Express server handles uncaught exceptions:
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
server.close(() => process.exit(1));
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
server.close(() => process.exit(1));
});
Integrating with Claude Code Workflows
When building Express applications with Claude Code, these error handling patterns integrate naturally with your development workflow. The tdd skill helps you write tests for error scenarios before implementing handlers, ensuring your error paths work correctly.
For API documentation, the pdf skill can generate API reference documents that include error response schemas. This helps teams understand possible error codes and messages.
The supermemory skill preserves context about your error handling decisions across Claude Code sessions, making it easier to maintain consistent patterns across larger projects.
If you’re building forms that submit to Express backends, combine these patterns with the frontend-design skill to create user-friendly error displays that match your validation logic.
Summary
Effective Express middleware error handling requires several interconnected patterns:
- Custom error classes provide semantic meaning and consistent status codes
- Async wrappers prevent unhandled promise rejections
- Structured responses give clients predictable error formats
- Proper middleware ordering ensures errors reach handlers correctly
- Logging and shutdown handle critical errors gracefully
These patterns scale from small APIs to enterprise applications. Implement them early in your project to avoid retrofitting error handling later.
Related Reading
- Claude TDD Skill: Test-Driven Development Guide (2026) — Write tests for your Express error handlers before implementation to catch edge cases systematically.
- Claude Code Input Validation and Sanitization Patterns Guide — Pair error handling with solid input validation to prevent the errors before they need handling.
- Express to Fastify Migration with Claude Code (2026) — When your Express error handling outgrows the framework, migrate to Fastify with Claude Code assistance.
- Getting Started with Claude Skills — Learn the foundational Claude skills that power Express and Node.js development workflows.
Built by theluckystrike — More at zovo.one