Script Valley
Express.js: APIs and Middleware
Building RESTful APIsLesson 3.5

How to handle async route handlers without crashing Express

async await in Express, unhandled promise rejection, try catch boilerplate, asyncHandler wrapper, process uncaughtException, error propagation with next

Async Error Handling in Express

Express 4 does not catch promise rejections automatically. An unhandled rejection in an async route handler crashes the process or hangs the request.

The problem

// This will crash if fetchData() throws
app.get('/data', async (req, res) => {
  const data = await fetchData(); // unhandled rejection
  res.json(data);
});

Option 1 — try/catch every handler

app.get('/data', async (req, res, next) => {
  try {
    const data = await fetchData();
    res.json(data);
  } catch (err) {
    next(err); // pass to error handler
  }
});

Option 2 — asyncHandler wrapper (DRY)

const asyncHandler = fn => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

// Usage — no try/catch needed
app.get('/data', asyncHandler(async (req, res) => {
  const data = await fetchData();
  res.json(data);
}));

app.post('/users', asyncHandler(async (req, res) => {
  const user = await createUser(req.body);
  res.status(201).json(user);
}));

The asyncHandler wrapper is ~2 lines but eliminates try/catch boilerplate across every async route. Express 5 (released stable) handles this natively — but until your app uses Express 5, this wrapper is essential.