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#

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

router.get("/users", listUsers);
example.jsNo problems
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:

example.jsNo problems
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.
example.jswith { "allowedNames": ["listUsers"] }No problems
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:

example.jswith { "allowedNames": ["listUsers"] }1 problem
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.