Async Route Handlers

Express 5 Awaits Your Promises — Express 4 Didn't

Async Route Handlers

Express 5 supports async/await natively. Express 4 didn't — unhandled rejections crashed the request.

3 min read Level 2/5 #express#async#await
What you'll learn
  • Write async handlers in Express 5
  • Use express-async-handler in Express 4
  • Handle errors thrown by async code

In Express 4, an async handler that threw would crash without hitting your error middleware. Express 5 fixed it.

Express 5

app.get("/users/:id", async (req, res) => {
  const user = await db.users.findById(req.params.id);
  if (!user) {
    res.status(404).json({ error: "not found" });
    return;
  }
  res.json(user);
});

Throws or rejections propagate to your error middleware automatically.

app.get("/users/:id", async (req, res) => {
  const user = await db.users.findById(req.params.id);
  if (!user) throw new NotFoundError("user");
  res.json(user);
});

// Centralized error handler catches it:
app.use((err, req, res, next) => {
  if (err instanceof NotFoundError) {
    return res.status(404).json({ error: err.message });
  }
  res.status(500).json({ error: "internal" });
});

Express 4 — express-async-handler

In Express 4, wrap each async handler:

npm install express-async-handler
import asyncHandler from "express-async-handler";

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

Or roll your own:

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

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

Don’t Forget to await

Subtle bug:

// missing await — error from createUser is silently swallowed
app.post("/users", async (req, res) => {
  db.users.create(req.body);   // no await!
  res.status(201).end();
});

Always await async calls. ESLint’s no-floating-promises rule catches this.

Async Middleware Too

Middleware can also be async — same rules:

async function requireAuth(req, res, next) {
  try {
    req.user = await verifyToken(req.headers.authorization);
    next();
  } catch (err) {
    res.status(401).json({ error: "invalid token" });
  }
}

Or, even cleaner — throw and let the error middleware handle it.

Route Order Matters →