Catching an error in an async function in Node/Express
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Async route handlers in Express are convenient, but error handling depends on how the error reaches Express. If an asynchronous function throws or rejects and Express never receives that error, the request can hang or the process can log an unhandled rejection instead of returning a proper HTTP response.
The right pattern depends on your Express version. In Express 5, rejected async handlers are forwarded automatically. In Express 4, you usually need to pass errors to next yourself or wrap the handler.
Express 5: Async Errors Are Forwarded Automatically
In Express 5, an async route handler that throws or rejects is treated as an error and forwarded to the error middleware.
If getUserById throws, Express 5 will call the error middleware automatically.
Express 4: You Must Forward Async Errors Explicitly
In Express 4, an async handler does not automatically hand rejected promises to Express. The usual pattern is a try and catch block.
The key step is next(err). That is what transfers control to Express error-handling middleware.
A Reusable Async Wrapper
To avoid repeating try and catch in every route, many Express 4 codebases use a small wrapper.
This is a clean pattern because it keeps route handlers focused on business logic while ensuring promise rejections are forwarded correctly.
Error Middleware Must Come Last
Express only uses error middleware if it is registered after your routes and other middleware.
Also remember that error middleware has four arguments: (err, req, res, next). Without the err parameter, Express does not treat it as an error handler.
What About Errors in Timers or Callbacks?
Errors thrown inside plain callbacks such as setTimeout are different from rejected promises. Express cannot catch them automatically unless you route them back through next.
This matters because not all asynchronous code in Node is promise-based.
Returning After next(err)
A subtle bug appears when code calls next(err) and then keeps going. That can lead to double responses.
Using return next(err) is a simple way to stop the handler after the error is forwarded.
Common Pitfalls
One common mistake is assuming async and await automatically make Express catch everything. That is only true for rejected route-handler promises in Express 5, not for every asynchronous callback pattern.
Another issue is forgetting to register error middleware last. If it is placed before the routes, it will not handle the route errors the way you expect.
It is also easy to call res.send(...) and then later call next(err) after headers have already been sent. At that point, error handling becomes more complicated and the connection may already be partially committed.
Finally, do not swallow errors in a catch block without responding or calling next. That creates hanging requests.
Summary
- In Express 5, rejected async route handlers are forwarded to error middleware automatically.
- In Express 4, use
tryandcatchwithnext(err)or wrap handlers with a reusable async helper. - Error middleware must appear after routes and must use the four-argument signature.
- Promise rejection handling and plain callback error handling are not the same thing.
- A good async error pattern prevents hung requests and keeps route code readable.

