Handling Async Operations

1. Using async/await in Route Handlers

Example: Express 5 native

app.get("/users/:id", async (req, res) => {
  const user = await db.users.findById(req.params.id);
  if (!user) throw new HttpError(404);
  res.json(user);
});
Express VersionAuto-forward errors?
4.xNo — wrap or use express-async-errors
5.xYes (since 5.0)

2. Creating Async Middleware

Example: Async auth

export async function authenticate(req, res, next) {
  const token = (req.get("Authorization") || "").replace("Bearer ", "");
  if (!token) throw new HttpError(401);
  const payload = await verifyJwt(token);
  req.user = await db.users.findById(payload.sub);
  next();
}

3. Handling Promise Rejections

SourceExpress 5 behavior
Awaited rejectionForwarded to error MW
Unawaited promiseNOT caught — fires on unhandledRejection
Setimer / event callbackNOT caught — wrap manually
Warning: Always await promises inside handlers, or attach .catch(next). Fire-and-forget promises bypass Express error handling.

4. Using express-async-handler Package

Example: Wrapper utility

import asyncHandler from "express-async-handler";

app.get("/users", asyncHandler(async (req, res) => {
  const users = await db.users.findAll();
  res.json(users);
}));

5. Implementing try-catch Wrappers

Example: Inline try/catch

app.get("/data", async (req, res, next) => {
  try {
    const data = await fetchExternal();
    res.json(data);
  } catch (err) {
    next(err);  // delegate to error middleware
  }
});

6. Forwarding Async Errors

PatternNotes
Throw in async handlerExpress 5: auto-forwarded
next(err)Explicit forward
Promise.catch(next)Tail-call forward

7. Using Promise.all() for Parallel Operations

Example: Parallel fetch

app.get("/dashboard", async (req, res) => {
  const [user, stats, notifications] = await Promise.all([
    db.users.findById(req.user.id),
    statsService.fetch(req.user.id),
    notificationService.list(req.user.id)
  ]);
  res.json({ user, stats, notifications });
});
MethodBehavior
Promise.all([...])Reject on first failure
Promise.allSettled([...])Wait for all (no rejection)
Promise.race([...])Resolve with first settled
Promise.any([...])First fulfilled (skip rejected)

8. Implementing Async Validation

Example: zod async refinement

const userSchema = z.object({
  email: z.string().email().refine(
    async (email) => !(await db.users.findByEmail(email)),
    { message: "Email already in use" }
  )
});
const data = await userSchema.parseAsync(req.body);

9. Handling Async Database Operations

Example: Transaction

app.post("/transfer", async (req, res) => {
  const { from, to, amount } = req.body;
  await db.$transaction(async (tx) => {
    await tx.account.update({ where: { id: from }, data: { balance: { decrement: amount } } });
    await tx.account.update({ where: { id: to },   data: { balance: { increment: amount } } });
  });
  res.sendStatus(204);
});

10. Using Promise.allSettled() for Multiple Operations

Example: Best-effort parallel

app.post("/notify", async (req, res) => {
  const results = await Promise.allSettled([
    sendEmail(req.body),
    sendSms(req.body),
    sendPush(req.body)
  ]);
  const failures = results
    .map((r, i) => ({ channel: ["email","sms","push"][i], ok: r.status === "fulfilled", reason: r.reason?.message }))
    .filter(r => !r.ok);
  res.json({ sent: results.length - failures.length, failures });
});