oxc-project/oxc · error · OxcDiagnostic

Express endpoint handler for `{endpoint}` should not be `asy

Error message

Express endpoint handler for `{endpoint}` should not be `async`.

What it means

Oxlint rule `oxc/no_async_endpoint_handlers` flags `async` functions passed as Express route handlers. Express 4 does not catch promise rejections from handlers: a rejected async handler becomes an unhandled promise rejection, skipping the error middleware (`next(err)` is never called) and potentially crashing Node on `unhandledRejection`. The message interpolates the endpoint path (or the generic form when the path is not a static string), and the note says to disable the rule on Express 5, which handles rejections natively.

Source

Thrown at crates/oxc_linter/src/rules/oxc/no_async_endpoint_handlers.rs:78

        // Shouldn't happen, since separate declaration/registration requires an
        // identifier to be bound
        (Some(span), None) => &[
            async_span.label("Async handler is declared here"),
            span.primary_label(registered_label),
        ],
        // `app.get('/foo', async function foo(req, res) {});`
        (None, Some(name)) => &[async_span.label(format!("Async handler '{name}' is used here"))],

        // `app.get('/foo', async (req, res) => {});`
        (None, None) => &[async_span.label("Async handler is used here")],
    };

    let warning = endpoint.map_or_else(
        || "Express endpoint handler should not be `async`.".to_string(),
        |endpoint| format!("Express endpoint handler for `{endpoint}` should not be `async`."),
    );

    OxcDiagnostic::warn(warning)
        .with_labels(labels.iter().cloned())
        .with_help(
            "Wrap the async handler and forward errors to `next()` (e.g. `(req, res, next) => Promise.resolve(handler(req, res, next)).catch(next)`).\nExpress does not automatically handle rejected promises from async handlers, which results in unhandled promise rejections and server crashes.",
        ).with_note(
            "If you're on Express 5, disable this rule. To allow specific functions, add their names to `allowedNames`."
        )
}

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Disallows the use of `async` functions as Express endpoint handlers.
    ///
    /// ### Why is this bad?
    ///
    /// Before v5, Express will not automatically handle Promise rejections from
    /// handler functions with your application's error handler. You must
    /// instead explicitly pass the rejected promise to `next()`.

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Wrap handlers so rejections reach `next`: `const wrap = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next)` and register `wrap(asyncHandler)`
  2. Or use a library: `express-async-handler` or the `express-async-errors` shim imported once at startup
  3. Use try/catch inside the handler and call `next(err)` in the catch block
  4. Add the handler's name to the `allowedNames` option for deliberate exceptions
  5. On Express 5, disable the rule: `"oxc/no_async_endpoint_handlers": "off"`

Example fix

// before
app.get('/users', async (req, res) => {
  const users = await db.all('SELECT * FROM users');
  res.json(users);
});

// after
const wrap = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get('/users', wrap(async (req, res) => {
  const users = await db.all('SELECT * FROM users');
  res.json(users);
}));
Defensive patterns

Strategy: try-catch

Try / catch

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

app.get('/users', wrap(async (req, res) => {
  res.json(await db.all());
}));
// all handler rejections are forwarded to the Express error middleware

Prevention

When it happens

Trigger: `app.get('/foo', async (req, res) => { ... })`, `router.post('/bar', async function handler(req, res) { ... })`, or the same as middleware-array entries — i.e. any async function value passed where Express expects a handler, with the route path known at lint time. Config `allowedNames` exempts specifically named functions.

Common situations: Express 4 apps with async/await DB or fetch calls inside handlers where errors vanish past the error middleware; Node 15+ processes exiting on unhandled rejections; teams upgrading to Express 5 but keeping the rule enabled.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/19569d680385b6f1. Report an issue: GitHub.