Skip to content

Mean 17 Error Handling Middleware

DodaTech 7 min read

title: "Error Handling Middleware — Building Robust Express APIs" description: "Implement centralized error handling middleware in Express for the MEAN Stack with custom error classes, consistent responses, and logging." weight: 27 date: 2026-06-28 lastmod: 2026-06-28 tags: [frameworks, mean]

Centralized error handling middleware in Express catches errors from all routes, formats them consistently, and logs them for debugging.

What You'll Learn

You will create a centralized error handler with custom error classes, async error wrapper, consistent error response format, and environment-aware error details.

Why It Matters

Without centralized error handling, each route must handle errors individually, leading to inconsistent responses and repetitive code.

Real-World Use

DodaZIP's API uses a centralized error handler that catches all errors, logs them to a monitoring service, and returns consistent JSON responses to the Angular frontend.

flowchart LR
    A[Route Handler] --> B{Throws Error?}
    B -->|Yes| C[Error Middleware]
    B -->|No| D[JSON Response]
    C --> E{Error Type}
    E --> F[Validation Error]
    E --> G[Not Found Error]
    E --> H[Auth Error]
    E --> I[Server Error]
    F --> J[Consistent JSON Error]
    G --> J
    H --> J
    I --> J
    J --> K[Log Error]
    style C fill:#4a90d9,color:#fff

Custom Error Classes

Create custom error classes with HTTP status codes.

// backend/utils/AppError.js
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);
  }
}

class NotFoundError extends AppError {
  constructor(resource = 'Resource') {
    super(`${resource} not found`, 404);
  }
}

class ValidationError extends AppError {
  constructor(message) {
    super(message, 400);
  }
}

class UnauthorizedError extends AppError {
  constructor(message = 'Authentication required') {
    super(message, 401);
  }
}

class ForbiddenError extends AppError {
  constructor(message = 'Insufficient permissions') {
    super(message, 403);
  }
}

module.exports = {
  AppError,
  NotFoundError,
  ValidationError,
  UnauthorizedError,
  ForbiddenError
};

Expected output: Custom error classes extend Error with HTTP status codes. Use these classes to throw consistent errors with appropriate status codes.

Async Error Wrapper

Wrap async route handlers to catch errors automatically.

// backend/utils/asyncHandler.js
function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

module.exports = asyncHandler;

// Usage
const asyncHandler = require('../utils/asyncHandler');

router.get('/users', asyncHandler(async (req, res) => {
  const users = await User.find();
  res.json(users);
}));

// Without wrapper, you need try/catch in every route
router.get('/users', async (req, res, next) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (error) {
    next(error);
  }
});

Expected output: The asyncHandler wraps async route handlers and catches any errors, passing them to the next middleware (the error handler).

Centralized Error Handler Middleware

Create the main error handling middleware.

// backend/middleware/errorHandler.js
const { AppError } = require('../utils/AppError');

function errorHandler(err, req, res, next) {
  // Log error
  console.error('Error:', err);

  // Default to 500 internal server error
  let statusCode = err.statusCode || 500;
  let message = err.message || 'Internal Server Error';

  // Handle specific error types
  if (err.name === 'ValidationError') {
    // Mongoose validation error
    statusCode = 400;
    const messages = Object.values(err.errors).map(e => e.message);
    message = messages.join('. ');
  }

  if (err.name === 'CastError') {
    // Mongoose invalid ObjectId
    statusCode = 400;
    message = 'Invalid ID format';
  }

  if (err.code === 11000) {
    // MongoDB duplicate key error
    statusCode = 409;
    const field = Object.keys(err.keyValue)[0];
    message = `Duplicate value for ${field}. This ${field} is already in use.`;
  }

  if (err.name === 'JsonWebTokenError') {
    statusCode = 401;
    message = 'Invalid token. Please log in again.';
  }

  if (err.name === 'TokenExpiredError') {
    statusCode = 401;
    message = 'Token expired. Please log in again.';
  }

  if (err.name === 'MulterError') {
    statusCode = 400;
    if (err.code === 'LIMIT_FILE_SIZE') {
      message = 'File too large';
    } else {
      message = err.message;
    }
  }

  // Response in development vs production
  const response = {
    success: false,
    error: message
  };

  if (process.env.NODE_ENV === 'development') {
    response.stack = err.stack;
  }

  res.status(statusCode).json(response);
}

module.exports = errorHandler;

Expected output: The error handler catches all errors, determines the appropriate status code, formats the response consistently, and includes stack traces in development.

Using the Error Handler

Apply the error handler in the server file.

// backend/server.js
const errorHandler = require('./middleware/errorHandler');

// Define routes
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);

// Error handler MUST be after routes
app.use(errorHandler);

// Handle unhandled routes
app.all('*', (req, res) => {
  res.status(404).json({
    success: false,
    error: `Route ${req.originalUrl} not found`
  });
});

Expected output: The error handler is the last middleware. Any error thrown or passed via next() in routes is caught and formatted consistently.

Using Custom Errors in Routes

Throw custom errors in route handlers for consistent error handling.

const asyncHandler = require('../utils/asyncHandler');
const { NotFoundError, ValidationError, ForbiddenError } = require('../utils/AppError');

// Example route using custom errors
router.get('/:id', asyncHandler(async (req, res) => {
  const product = await Product.findById(req.params.id);

  if (!product) {
    throw new NotFoundError('Product');
  }

  if (product.status === 'archived' && req.user.role !== 'admin') {
    throw new ForbiddenError('Archived products can only be viewed by administrators');
  }

  res.json({ success: true, data: product });
}));

// Example with validation
router.post('/', asyncHandler(async (req, res) => {
  const { name, price } = req.body;

  if (!name || name.length < 2) {
    throw new ValidationError('Product name must be at least 2 characters');
  }

  if (!price || price <= 0) {
    throw new ValidationError('Product price must be positive');
  }

  const product = await Product.create(req.body);
  res.status(201).json({ success: true, data: product });
}));

Expected output: Routes throw typed errors. The error handler catches them and returns appropriate status codes and messages.

Common Mistakes

  1. Not placing error handler after routes: Express middleware runs in order. Error handler must be the last middleware to catch errors from all routes.

  2. Not wrapping async routes: Express does not catch errors from async functions automatically. Without asyncHandler, async errors are unhandled.

  3. Exposing stack traces in production: Stack traces reveal implementation details. Only include them in development responses.

  4. Catching errors and not passing to next(): If you catch an error in a route, pass it to next(error) or throw it. Otherwise, the error handler never sees it.

  5. Not handling specific error types: Mongoose validation errors, CastError, and duplicate key errors have specific error objects. Handle each appropriately.

Practice Questions

  1. Why should error handling middleware be placed last?

Express executes middleware in order. The error handler must be defined after all routes to catch errors from any route.

  1. What is the purpose of the asyncHandler wrapper?

It catches errors from async route handlers and passes them to the error handling middleware via next(error).

  1. How do you differentiate between operational and programmer errors?

Operational errors (invalid input, not found) are expected. Programmer errors (bugs) should crash the process in development.

  1. What status code should you return for validation errors?

400 Bad Request. Validation errors mean the client sent invalid data.

  1. How do you handle MongoDB duplicate key errors?

Check for error.code === 11000. Return 409 Conflict with a message indicating which field is duplicated.

Challenge

Implement a complete error handling system with: custom AppError classes, asyncHandler wrapper, centralized error handler that handles ValidationError, CastError, duplicate key, JWT errors, Multer errors, and a 404 handler.

Frequently Asked Questions

Should I handle all errors in one middleware?

Yes. One centralized error handler is cleaner than try/catch in every route. The asyncHandler wrapper makes this pattern automatic.

How do I log errors?

Use console.error in development. In production, integrate with a logging service like Winston, Sentry, or Datadog.

{{< faq "How do I handle 404 for unknown routes?" >> Add an app.all('*') handler after all routes that returns a 404 JSON response. {{< /faq >}}

What is the difference between operational and programmer errors?

Operational errors are expected (invalid input, not found). Programmer errors are bugs (undefined variable). Operational errors are handled gracefully. Programmer errors should crash in development.

How do I test error handling?

Create integration tests that trigger each error type. Verify the status code and response format match expectations.

Mini Project

Build a complete error handling system for a REST API with: asyncHandler for all routes, AppError classes for 400, 401, 403, 404, centralized error handler covering Mongoose, JWT, and Multer errors, and a 404 handler for unknown routes.

What's Next

Learn about Deployment options for the MEAN stack application.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro