npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

maestro-express-async-errors

v1.4.0

Published

TypeScript-first async error wrapper for Express, with optional Express 4 global patch and domain error routing.

Downloads

323

Readme

Maestro for Express async errors

TypeScript-first helpers so rejected promises and thrown errors in Express handlers reach your error middleware.

Zero runtime dependencies. Peer: express >=4.16.2.

When to use

| Setup | Recommendation | | --- | --- | | Express 4 | Use maestro() / asyncHandler, or opt-in global patch via register | | Express 5 | Async errors are native — wrappers are optional; maestro.from still helps for domain errors | | New projects | Prefer Express 5; keep Maestro only if you want typed domain error routing |

Install

npm install maestro-express-async-errors

Node.js >=18.

Three modes

1. Per-route wrapper (Express 4 and 5)

Drop-in compatible with express-async-handler:

import { maestro, asyncHandler } from "maestro-express-async-errors";

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

// alias
app.get("/health", asyncHandler(async (_req, res) => {
  res.send("ok");
}));

2. Batch wrap — maestro.all

app.post("/products", ...maestro.all([validationFn, createProductFn]));

3. Global patch (Express 4 only) — opt-in

Same DX as express-async-errors. Does nothing on Express 5+ (native promise support; patching would risk double next(err)).

import "maestro-express-async-errors/register";
// or: import { maestro } from "maestro-express-async-errors"; maestro.patch();

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

Patch touches Express 4 router internals. After upgrading to Express 5, remove the register import.

Domain errors — maestro.from

Define a typed domain error

Keep errors in your app (Maestro does not ship HTTP error classes). Use a class so instanceof and TypeScript agree:

class UserNotFoundError extends Error {
  readonly code = "USER_NOT_FOUND" as const;
  readonly statusCode = 404;

  constructor(readonly userId: string) {
    super(`User ${userId} not found`);
    this.name = "UserNotFoundError";
  }
}

Throw in the route, map in from

maestro.from narrows err to the class you pass. You get userId, code, statusCode without casting:

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

app.use(
  maestro.from(UserNotFoundError, (err, req, res, next) => {
    // err is UserNotFoundError
    res.status(err.statusCode).json({
      code: err.code,
      userId: err.userId,
      message: err.message,
    });
  })
);

app.use((err, req, res, next) => {
  res.status(500).json({ error: "internal" });
});

Unmatched errors fall through to the next error middleware. Stack several maestro.from(...) calls for each domain type; keep one generic handler last.

Optional pattern for your codebase (not exported by Maestro):

abstract class DomainError extends Error {
  abstract readonly code: string;
  abstract readonly statusCode: number;
}

Compared to alternatives

  • express-async-handler — same per-route wrap idea; Maestro adds TypeScript types, maestro.from, and maestro.all.
  • express-async-errors — same global-patch idea on Express 4; Maestro’s patch is opt-in (/register), no-op on Express 5, and ships with the wrapper API in one package.

Migrate from competitors

From express-async-handler

Drop-in: keep calling asyncHandler, change the import. Or rename to maestro.

// before
import asyncHandler from "express-async-handler";

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

// after
import { asyncHandler } from "maestro-express-async-errors";
// or: import { maestro as asyncHandler } from "maestro-express-async-errors";

app.get("/users/:id", asyncHandler(async (req, res) => {
  res.json(await getUser(req.params.id));
}));
npm uninstall express-async-handler
npm install maestro-express-async-errors

Optional after the swap: map domain errors with maestro.from instead of a single catch-all full of instanceof.

From express-async-errors

Replace the side-effect import. Route handlers stay the same (no per-route wrap).

// before
import "express-async-errors";
import express from "express";

const app = express();
app.get("/users", async (_req, res) => {
  res.json(await User.findAll());
});

// after (Express 4)
import "maestro-express-async-errors/register";
import express from "express";

const app = express();
app.get("/users", async (_req, res) => {
  res.json(await User.findAll());
});
npm uninstall express-async-errors
npm install maestro-express-async-errors

Put register before you mount routes, same habit as before.

On Express 5, drop the patch import entirely. Native async errors cover that job. Keep Maestro only if you want maestro.from / wrappers.

From both at once

Some apps use the global patch and wrap selected routes. With Maestro you pick one style (or mix on purpose):

  1. Global: import "maestro-express-async-errors/register" (Express 4 only).
  2. Per route: maestro / asyncHandler where you want it explicit.
  3. Domain: maestro.from(SomeError, handler) above your generic error middleware.

You do not need two packages anymore.

Presence checklist (maintainers)

  • [ ] Publish / refresh docs/why-maestro.md (Dev.to or blog)
  • [ ] Confirm NPM package description after release
  • [ ] Triage open GitHub issues/PRs
  • [ ] See docs/publishing.md for OIDC release setup

License

MIT