Skip to content
NewBun 1.4.5 is out. Release notes
AphrodyBun

Linter · Rules

oxc/no-async-endpoint-handlers

Disallow async functions as Express route handlers and middleware

Express 4 ignores the promise that an async handler returns. If the handler throws, or awaits something that rejects, your error middleware never runs. The request hangs, and the unhandled rejection can crash the process.

The rule checks .get(), .post(), .put(), .patch(), .delete(), .head(), .options(), .all(), and .use() on any object. A function written in the call is only reported if its parameters have the usual names, such as (req, res) or (err, req, res, next). A handler passed by name is looked up in the same file.

Configure

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"oxc/no-async-endpoint-handlers" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"oxc/no-async-endpoint-handlers" = ["error", {
  allowedNames = [], # list of strings
}]

Examples

Reported:

app.get("/users/:id", async (req, res) => {
  const user = await db.findUser(req.params.id);
  res.json(user);
});
async function listUsers(req, res) {
  res.json(await db.allUsers());
}

router.get("/users", listUsers);

Not reported:

app.get("/users/:id", (req, res, next) => {
  db.findUser(req.params.id)
    .then(user => res.json(user))
    .catch(next);
});

A handler wrapped in a call isn't reported, so a helper that passes rejections to next works too:

const wrap = handler => (req, res, next) => handler(req, res, next).catch(next);

app.get(
  "/users/:id",
  wrap(async (req, res) => {
    res.json(await db.findUser(req.params.id));
  }),
);

Options

OptionDefaultWhat it does
allowedNames[]Names of async functions you can use as handlers.
async function listUsers(req, res) {
  res.json(await db.allUsers());
}

router.get("/users", listUsers);

The name is the function's own name. An arrow function doesn't have one, so it's reported even if the variable that holds it is in the list:

const listUsers = async (req, res) => {
  res.json(await db.allUsers());
};

router.get("/users", listUsers);

When not to use it

Express 5 passes a rejected promise to next by itself, so async handlers are safe there.