express 5 async error handling: working versions (tested 2026-10-18)
Use express 5 with supertest to forward rejected async handlers to a JSON error middleware, with router-level params, a JSON 404 handler and offline tests.
passed3 passed · 0 failed2026-10-18 03:58 UTC- Status
- passed
- Last passed
- 2026-10-18 03:58 UTC
- Tests
- 3 / 3
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 39095442401
The code
Exactly the files that passed. Your agent can pull them with get_recipe("express5-async-error-handling") (install), or see them on GitHub.
{
"name": "express5-async-error-handling",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"express": "5.3.0",
"supertest": "7.3.1"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
import express from "express";
import type { NextFunction, Request, Response } from "express";
export function createApp() {
const app = express();
app.use(express.json());
const orders = express.Router({ mergeParams: true });
orders.get("/:orderId", (req: Request, res: Response) => {
const { userId, orderId } = req.params;
res.json({ userId, orderId });
});
app.use("/users/:userId/orders", orders);
app.get("/boom", async () => {
throw new Error("kaboom");
});
app.use((req: Request, res: Response) => {
res.status(404).json({ error: "not_found", path: req.path });
});
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
res.status(500).json({ error: "internal_error", message: err.message });
});
return app;
}
import { test } from "node:test";
import assert from "node:assert/strict";
import request from "supertest";
import { createApp } from "../src/app.ts";
test("resolves async handler with router-level params", async () => {
const app = createApp();
const res = await request(app).get("/users/u1/orders/o2");
assert.equal(res.status, 200);
assert.deepEqual(res.body, { userId: "u1", orderId: "o2" });
});
test("rejected async handler reaches error middleware without try/catch", async () => {
const app = createApp();
const res = await request(app).get("/boom");
assert.equal(res.status, 500);
assert.deepEqual(res.body, { error: "internal_error", message: "kaboom" });
});
test("unknown route returns JSON 404", async () => {
const app = createApp();
const res = await request(app).get("/nope");
assert.equal(res.status, 404);
assert.deepEqual(res.body, { error: "not_found", path: "/nope" });
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
cors express preflight
Configure cors with express to reflect allowed origins, answer OPTIONS preflight with Access-Control-Allow-* headers, and deny a disallowed origin, tested offline with supertest.
express-openapi-validator request and response validation
Validate an Express app against an inline OpenAPI 3 spec using express-openapi-validator, returning validation errors as JSON and testing valid and invalid requests offline with supertest.
express-validator request checks with supertest
Validate Express JSON bodies and query params with express-validator, returning field errors, tested with supertest offline.
http-errors with Express error middleware
Express routes that throw http-errors and reject asynchronously, mapped by a central middleware to RFC 7807 problem details with expose-aware messages and a 500 fallback, tested offline with supertest.