Centralized Error Handling

Funnel all errors to one error-handling middleware for consistent responses.

Syntaxapp.use((err, req, res, next) => { ... })

Rather than writing try/catch responses in every route, funnel errors to a single error-handling middleware at the bottom of your app. Routes just call next(err), and the handler decides how to respond.

A common pattern is a custom error class that carries a status code, so the handler knows what to send.

Example

Example · javascript
class AppError extends Error {
  constructor(message, status) {
    super(message);
    this.status = status;
  }
}

app.get('/posts/:id', (req, res, next) => {
  const post = null; // pretend not found
  if (!post) return next(new AppError('Post not found', 404));
  res.json(post);
});

app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ error: err.message });
});

When to use it

  • A REST API uses a centralised error middleware to ensure every unhandled error returns a JSON body instead of an HTML Express error page.
  • A team creates an AppError class with a status field so controllers can throw domain errors and the single error handler sets the HTTP status automatically.
  • A developer wraps all async route handlers in a try/catch that calls next(err) to funnel every promise rejection to the global error middleware.

More examples

Centralised error middleware

Registers a 4-argument error middleware that catches any error passed via next(err) and returns a consistent JSON error body.

Example · js
// Must be the LAST app.use() call
app.use((err, req, res, next) => {
  const status = err.status || 500;
  res.status(status).json({
    error: {
      message: err.message || 'Internal Server Error',
      status,
    },
  });
});

Custom AppError class

Defines AppError with a status property so route handlers can throw semantically typed errors that the handler serialises.

Example · js
class AppError extends Error {
  constructor(message, status = 500) {
    super(message);
    this.status = status;
    this.name = 'AppError';
  }
}

app.get('/items/:id', (req, res, next) => {
  const item = items.find(i => i.id == req.params.id);
  if (!item) return next(new AppError('Item not found', 404));
  res.json(item);
});

app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ error: err.message });
});

404 catch-all handler

A final middleware creates a 404 AppError for any unmatched route, which the error handler then serialises.

Example · js
// Place after all routes, before the error handler
app.use((req, res, next) => {
  next(new AppError(`Cannot ${req.method} ${req.path}`, 404));
});

// Error handler last
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ error: err.message });
});

Discussion

  • Be the first to comment on this lesson.