08 Error Middleware
title: Error Middleware in Node.js REST APIs weight: 18 date: 2026-06-28 lastmod: 2026-06-28 description: Master error handling middleware in Express including custom error classes, centralized error handler, async error wrapping, and consistent error response formatting. tags: [api-development, nodejs]
Error middleware in Express uses a 4-parameter function (err, req, res, next) that catches errors from the entire application, formats them consistently, and returns structured error responses with appropriate status codes.
```mermaid
flowchart TD
A[Route Handler] -->|throws error| B[Error Middleware]
B --> C{Error Type}
C --> D[Validation Error -> 400]
C --> E[Auth Error -> 401]
C --> F[Not Found -> 404]
C --> G[Server Error -> 500]
D --> H[Consistent Error Response]
style A fill:#e1f5fe
style B fill:#ffcdd2
style H fill:#fff9c4
Create custom error classes that include statusCode and isOperational properties. Throw these errors from route handlers and services. The centralized error middleware catches them and formats the response. Operational errors (validation, auth) are handled gracefully. Programming errors (bugs) return a generic 500.
Think of error middleware like a hospital emergency room. Each error (patient) is triaged based on severity. Minor errors (validation) get treated and released quickly. Major errors (server bugs) go to intensive care. The error middleware is the triage nurse who directs each patient.
Example: Custom Error Classes
class AppError extends Error {
constructor(message, statusCode, code = 'INTERNAL_ERROR') {
super(message);
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404, 'NOT_FOUND');
}
}
class ValidationError extends AppError {
constructor(details) {
super('Validation failed', 400, 'VALIDATION_ERROR');
this.details = details;
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Authentication required') {
super(message, 401, 'UNAUTHORIZED');
}
}
module.exports = { AppError, NotFoundError, ValidationError, UnauthorizedError };
Example: Centralized Error Handler
const { AppError } = require('./errors');
const errorHandler = (err, req, res, next) => {
console.error(`[${new Date().toISOString()}] Error:`, err.message);
// Default to 500 for unexpected errors
if (!(err instanceof AppError)) {
err = new AppError('Internal server error', 500);
err.isOperational = false;
}
const response = {
status: 'error',
error: {
code: err.code,
message: err.isOperational ? err.message : 'Something went wrong'
}
};
// Add validation details
if (err.details) {
response.error.details = err.details;
}
// Include stack trace in development
if (process.env.NODE_ENV === 'development') {
response.error.stack = err.stack;
}
res.status(err.statusCode).json(response);
};
// Register as last middleware
app.use(errorHandler);
Example: Using Errors in Routes
const { NotFoundError, ValidationError } = require('./errors');
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await findUser(req.params.id);
if (!user) {
throw new NotFoundError('User');
}
res.json({ status: 'success', data: user });
} catch (error) {
next(error);
}
});
app.post('/api/users', async (req, res, next) => {
try {
if (!req.body.name) {
throw new ValidationError([{ field: 'name', issue: 'Name is required' }]);
}
const user = await createUser(req.body);
res.status(201).json({ status: 'success', data: user });
} catch (error) {
next(error);
}
});
Common Mistakes
- Not passing errors to next() — Throwing in async routes without wrapping in try/catch or using express-async-errors results in unhandled promise rejections.
- Exposing stack traces in production — Stack traces reveal internal code structure. Include them only in development responses.
- Missing isOperational flag — Distinguish between expected errors (validation failures) and unexpected errors (database connection failure). Log unexpected errors differently.
- Catching errors locally and not forwarding — Catching an error in a route handler without calling next(error) swallows it. Always forward unexpected errors.
- Not handling async errors — Errors thrown from async functions are not caught by Express error middleware. Use a wrapper or express-async-errors package.
Practice Questions
- How do you create a custom error class in Express?
- What is the difference between operational and programmer errors?
- Why must error-handling middleware have 4 parameters?
- How do you handle errors from async route handlers?
- Challenge: Build a complete error handling system with custom error classes, async error wrapper middleware, centralized error handler, and a logger that separates operational errors from programmer errors.
FAQ
Mini Project
Build a complete error handling middleware suite for an Express API. Include custom error classes (NotFound, Validation, Unauthorized, Forbidden), a centralized error handler with environment-aware responses, async error wrapping middleware, and structured error logging.
What's Next
Now learn about validation with Joi and Zod in Building REST APIs with Node.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro