CPCODELAB
Express

Centralized Error Handling and Async Wrappers

10 min read

Centralized Error Handling and Async Wrappers

In Express 4, unhandled Promise rejections inside route handlers do not automatically reach the error-handling middleware — you must catch them manually and pass the error to next. Express 5 fixes this, but many production codebases still run Express 4.

The asyncWrapper Utility

A thin higher-order function called asyncWrapper (sometimes spelled catchAsync) wraps every async route handler so that any rejected Promise is forwarded to next(err) automatically.

js
// src/middleware/asyncWrapper.js
function asyncWrapper(fn) {
  return function (req, res, next) {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

module.exports = asyncWrapper;

// Usage in a route file
const asyncWrapper = require("../middleware/asyncWrapper");

router.get("/orders", asyncWrapper(async (req, res) => {
  const orders = await db.order.findMany();
  res.json(orders); // any throw is forwarded to next(err)
}));

A Custom ApiError Class

js
// src/lib/ApiError.js
class ApiError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
  }
}

module.exports = ApiError;

// Throwing in a route
const ApiError = require("../lib/ApiError");

router.get("/items/:id", asyncWrapper(async (req, res) => {
  const item = await db.item.findUnique({ where: { id: Number(req.params.id) } });
  if (!item) throw new ApiError(404, "Item not found");
  res.json(item);
}));
js
// src/middleware/errorHandler.js — register LAST in app.js
const ApiError = require("../lib/ApiError");

function errorHandler(err, req, res, next) {
  const status = err instanceof ApiError ? err.status : 500;
  const message = err.message || "Internal Server Error";
  console.error(`[${status}] ${message}`, err.stack);
  res.status(status).json({ error: message });
}

module.exports = errorHandler;

Keep ApiError, asyncWrapper, and errorHandler in src/lib/ or src/middleware/. Import them once in each router file — they eliminate almost all repetitive try/catch blocks across your codebase.

Ready to test yourself?
Practice Express— quiz & coding exercises