Skip to content

08 Error Middleware

DodaTech 4 min read

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

  1. Not passing errors to next() — Throwing in async routes without wrapping in try/catch or using express-async-errors results in unhandled promise rejections.
  2. Exposing stack traces in production — Stack traces reveal internal code structure. Include them only in development responses.
  3. Missing isOperational flag — Distinguish between expected errors (validation failures) and unexpected errors (database connection failure). Log unexpected errors differently.
  4. Catching errors locally and not forwarding — Catching an error in a route handler without calling next(error) swallows it. Always forward unexpected errors.
  5. 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

  1. How do you create a custom error class in Express?
  2. What is the difference between operational and programmer errors?
  3. Why must error-handling middleware have 4 parameters?
  4. How do you handle errors from async route handlers?
  5. 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

Why does error middleware need 4 parameters?

Express identifies error-handling middleware by the number of parameters. 4 parameters (err, req, res, next) tells Express it handles errors.

What is the express-async-errors package?

It patches Express to catch errors thrown from async route handlers, so you do not need try/catch wrappers. Install and require it once at the entry point.

Should I use try/catch in every route handler?

Either wrap in try/catch and call next(error), or use express-async-errors to catch automatically. The wrapper approach is more explicit.

How do I handle 404 routes in Express?

Add a catch-all middleware after all routes: app.use('*', (req, res) => res.status(404).json({error: 'Route not found'})).

What should I log for errors?

Log the error message, stack trace, request method, URL, user ID (if authenticated), and timestamp. Store in a centralized logging system.

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