Error Handling in Express 5

A
Author
19 Jun, 2026 1 min read 1.39K views

Express 5 finally handles async route handlers properly: if an async handler throws or returns a rejected promise, the error goes to your error middleware. No more express-async-errors or wrapping every route in try/catch.

Async handlers just work

app.get('/users/:id', async (req, res) => {
  const user = await db.users.find(req.params.id); // if this rejects, Express catches it
  if (!user) throw new HttpError(404, 'User not found');
  res.json(user);
});

A small error class

export class HttpError extends Error {
  constructor(status, message, details) {
    super(message);
    this.status = status;
    this.details = details;
  }
}

One error middleware at the end

// after all routes
app.use((req, res) => res.status(404).json({ error: 'Not found' }));

app.use((err, req, res, next) => {
  const status = err.status ?? 500;
  if (status >= 500) console.error(err); // log the details server-side

  res.status(status).json({
    error: status >= 500 ? 'Something went wrong' : err.message, // don't leak internals
    ...(err.details && { details: err.details }),
  });
});

Error middleware is recognised by having exactly four parameters, so keep next even if you don't use it.

Other Express 5 changes to know

  • Route path syntax changed: wildcards must be named, e.g. '/files/*path', and optional segments use braces: '/docs{/:section}'.
  • req.query is a getter and no longer writable.
  • res.status() only accepts valid HTTP status codes.
  • Some deprecated methods were removed (for example res.sendfile; use res.sendFile).

Don't forget the process level

process.on('unhandledRejection', (reason) => {
  console.error('Unhandled rejection', reason);
  process.exitCode = 1;
});
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.