express-async-errors
semver
>=3.0.0 <4.0.0postconditions7functions1last verified2026-04-17coverage score82%Postconditions — what we check
- require · async-error-not-forwarded-without-error-handlererrorWhenAn async route handler throws or rejects, but no Express error handler (4-argument middleware) is registeredThrows
Unhandled error — Express's default behavior sends a 500 response or crashes depending on Express versionRequired handlingApplication MUST register an Express error handler with the signature (err, req, res, next) AFTER all routes. Without it, async errors caught by express-async-errors have nowhere to go and Express will send a generic 500.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - require · async-error-forwardedinfoWhenAn async route handler throws or rejects after express-async-errors is requiredReturnsError is automatically passed to Express next(err). No manual next(err) call is required in the route handler.Required handlingNo action required — use the returned value as needed.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1]
- require · sync-errors-unaffectedinfoWhenA synchronous route handler throwsReturnsSynchronous throws in non-async route handlers are still caught by Express natively and forwarded to error handlers as beforeRequired handlingNo action required — use the returned value as needed.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1]
- require · express-v5-incompatibleerrorWhenProject uses Express v5 (express@^5.0.0) with express-async-errors installed. Express v5 relocated its internal Router and Layer modules to a separate pillarjs/router package, breaking the deep-require mechanism (require('express/lib/router')) that express-async-errors relies on. Requiring the package throws a fatal module-not-found error at application startup.Throws
Error: Cannot find module 'express/lib/router' (or equivalent module-not-found error) thrown synchronously when require('express-async-errors') is called. This crashes the entire Node.js process before any routes are served.Required handlingDo NOT use express-async-errors with Express v5. Express v5 natively supports async route handlers returning Promises — async errors are automatically forwarded to the next() error handler without any additional library. Migration: Simply remove express-async-errors from your project when upgrading to Express v5. // Express v5 — this works natively without express-async-errors: app.get('/users', async (req, res) => { const users = await User.findAll(); // rejection auto-forwarded to error handler res.json(users); }); // Error handler still required: app.use((err, req, res, next) => { res.status(500).json({ error: err.message }); });costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - require · esm-not-supportederrorWhenProject uses ES module syntax or ESM-strict loaders (e.g., Node.js 'type: module', ts-node with ESM loader @swc-node/register/esm, or Vitest with ESM configuration). express-async-errors is a CommonJS-only package that uses require() internally. Attempting to import it in an ES module context throws ReferenceError at startup.Throws
ReferenceError: require is not defined in ES module scope thrown when the import is processed. This is a fatal startup error.Required handlingOption 1 — Use CommonJS throughout your Express app (avoid 'type: module' in package.json). CommonJS is still the standard for Express applications: // package.json — do NOT set "type": "module" in Express apps using this library const createError = require('express-async-errors'); // CJS require — works Option 2 — Use a different approach: write explicit try/catch or next(err) wrappers instead of relying on this library, which works in both CJS and ESM: // Works in both CJS and ESM — no library needed app.get('/users', async (req, res, next) => { try { const users = await User.findAll(); res.json(users); } catch (err) { next(err); } }); Option 3 — Upgrade to Express v5 which natively supports async handlers in both ESM and CJS.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - require · param-middleware-patchedinfoWhenAsync param middleware callbacks registered via app.param() throw or reject. The library patches Router.prototype.constructor.param to wrap all param callbacks with the same async error catching mechanism applied to route handlers. This means async errors in param middleware are ALSO automatically forwarded to next(err) — no explicit try-catch needed. This is an undocumented feature not mentioned in the README.ReturnsParam middleware async errors are automatically forwarded to the Express error handler via next(err). Same behavior as route handler async errors — no explicit error handling in the param callback is required.Required handlingParam middleware errors are caught automatically, same as route errors. However, an error handler (4-arg middleware) must still be registered: require('express-async-errors'); // patches both routes AND param middleware app.param('userId', async (req, res, next, id) => { const user = await User.findById(id); // rejection auto-forwarded to error handler if (!user) throw new Error(`User ${id} not found`); // also forwarded req.user = user; }); app.use((err, req, res, next) => { res.status(404).json({ error: err.message }); }); Note: The error handler must still be registered — same requirement as for route handlers.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
- require · bundler-code-splitting-breaks-patcherrorWhenApplication is bundled with a code-splitting bundler (webpack with splitChunks, esbuild with splitting:true, Rollup with manualChunks). express-async-errors patches Express internals by mutating Layer.prototype at require-time. When code-splitting places the library and the route definitions in separate chunks, the prototype mutation may affect a different Layer instance than the one loaded by the route chunks. Result: the patch appears to work in development (no bundler) but silently fails in production builds — async errors in routes are NOT forwarded to error handlers and cause unhandled promise rejections.ReturnsAsync errors in route handlers are silently swallowed — NOT forwarded to the Express error handler. No crash, no 500 — the request hangs until timeout or the connection is dropped. This is worse than a crash because it is invisible in monitoring.Required handlingDisable code-splitting in your bundler configuration when using express-async-errors: // webpack.config.js — disable splitChunks to prevent cross-chunk prototype mutation module.exports = { optimization: { splitChunks: false, // disable code splitting } }; // esbuild — do not use splitting:true with express-async-errors esbuild.build({ splitting: false }); Alternative: ensure express-async-errors and all Express-using code are in the same chunk. Best alternative: avoid bundling server-side Node.js code (Node.js does not need bundling the way browsers do). Run Node.js directly or use ts-node/tsx for TypeScript.costhighin prodsilent failureusers seedegraded performancevisibilitysilentSources[7]
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]github.com/davidbanham/express-async-errorshttps://github.com/davidbanham/express-async-errors/blob/master/README.md
- [2]github.com/davidbanham/express-async-errorshttps://github.com/davidbanham/express-async-errors/issues/28
- [3]github.com/pillarjs/routerhttps://github.com/pillarjs/router
- [4]github.com/davidbanham/express-async-errorshttps://github.com/davidbanham/express-async-errors/issues/50
- [5]github.com/davidbanham/express-async-errorshttps://github.com/davidbanham/express-async-errors/blob/master/index.js
- [6]github.com/davidbanham/express-async-errorshttps://github.com/davidbanham/express-async-errors/blob/master/test.js
- [7]github.com/davidbanham/express-async-errorshttps://github.com/davidbanham/express-async-errors/issues/47
Need a different package?
Request a profile