@nreactive/express
v0.2.0
Published
Express middleware for @nreactive/core — automatic request-context propagation and error capture
Maintainers
Readme
@nreactive/express
Express 4/5 middleware for @nreactive/core — automatic request-context propagation and error capture for the nreactive AI error-fix pipeline.
- Wraps every request in an
AsyncLocalStoragecontext — method, url, request ID, scrubbed headers, andreq.userare attached to any error captured during handling. - Captures anything that reaches Express's error-handling chain (
next(err)+ thrown errors inside handlers). - Zero runtime dependencies of its own — just
@nreactive/core(peer) and Express. - Works with Express 4 and 5. Node ≥ 20.
Install
npm install @nreactive/core @nreactive/express
# or: pnpm add @nreactive/core @nreactive/express
# or: yarn add @nreactive/core @nreactive/express
# or: bun add @nreactive/core @nreactive/expressAI-assisted setup
Paste the prompt below into Claude Code, Cursor, Copilot, or any agent with file-write access. It has enough information to finish the integration end-to-end — no manual wiring required.
You are adding the
@nreactive/core+@nreactive/expressSDKs to this Express project.nreactiveis a production error-monitoring service that opens AI-generated pull requests to fix the errors it catches.
- Detect the package manager from the lockfile (
package-lock.json→ npm,pnpm-lock.yaml→ pnpm,yarn.lock→ yarn,bun.lockb→ bun).- Install
@nreactive/coreand@nreactive/expresswith that package manager.- Create a file
src/observability/nreactive.ts(or.jsif the project is not TypeScript) that importsinitfrom@nreactive/coreand calls it with{ appId: process.env.NREACTIVE_APP_ID, environment: process.env.NODE_ENV, release: process.env.APP_VERSION }. Export nothing — the module is used for its import side effect.- In the server entry file (commonly
src/index.ts,src/server.ts, or themainfield ofpackage.json), addimport "./observability/nreactive";as the very first import, before any framework imports includingexpress.- In the Express app setup:
- Call
app.use(requestHandler())from@nreactive/expressbefore any routes.- Call
app.use(errorHandler())from@nreactive/expressafter all routes and after any other error middleware.- Add
NREACTIVE_APP_ID=to.env.example(create it if missing) and document the variable in the README's environment-variables section. The user will obtain their App ID from https://nreactive.com/dashboard/apps.- Do not invent additional configuration. Restart the server and throw a test error from a route to confirm events appear in the nreactive dashboard.
Stop and ask if the project structure doesn't match these assumptions (for example, if the project is not using Express).
Quick start
// src/observability/nreactive.ts — imported FIRST in your entry file
import { init } from "@nreactive/core";
init({
appId: process.env.NREACTIVE_APP_ID!,
environment: process.env.NODE_ENV,
release: process.env.APP_VERSION,
});// src/server.ts
import "./observability/nreactive"; // ← must be the first import
import express from "express";
import { requestHandler, errorHandler } from "@nreactive/express";
const app = express();
app.use(requestHandler()); // FIRST — wraps each request in an async context
app.use(express.json());
app.get("/", (req, res) => {
res.send("hello");
});
app.use(errorHandler()); // LAST — captures anything that reaches the error chain
app.listen(3000);Get your App ID from the nreactive dashboard.
How it works
requestHandler()runs first and opens a freshAsyncLocalStorageframe for the request's async subtree. AnycaptureExceptioncall inside that subtree — including fromawaited work — automatically picks upmethod,url,requestId, scrubbedheaders, anduser(fromreq.user, if your auth middleware populates it).errorHandler()is an Express error middleware (4-arity) that catches every error passed tonext(err)or thrown synchronously from a handler. It classifies severity (see @nreactive/core) and callscaptureException. It always callsnext(err)so your existing error handling still runs.
Request IDs are sourced in this order:
X-Request-IDheaderX-Correlation-IDheader- Generated
randomUUID()
API
requestHandler(options?)
interface RequestHandlerOptions {
/** Header names to redact. Merged with @nreactive/core defaults. */
scrubHeaders?: string[];
/** Query param names to redact in the URL. Merged with core defaults. */
scrubQueryParams?: string[];
/** Emit an `http.server` breadcrumb for each request. Default: true. */
breadcrumbs?: boolean;
}Returns an Express handler (req, res, next) => void.
errorHandler(options?)
interface ErrorHandlerOptions {
/** Decide whether to capture a given error. Default: always true. */
shouldCapture?: (err: unknown, req: Request) => boolean;
}Returns an Express error middleware (err, req, res, next) => void — must be registered last.
Attaching user context
If your auth middleware puts a user on req.user, the request handler will propagate it automatically. Expected shape:
req.user = {
id: "u_42", // string or number — required for any user context to attach
email: "[email protected]", // optional
username: "alice", // optional
};To attach user info yourself (e.g. after loading from a session), import addContext from @nreactive/core:
import { addContext } from "@nreactive/core";
app.use(async (req, res, next) => {
const session = await getSession(req);
if (session) addContext({ user: { id: session.userId, email: session.email } });
next();
});Full example
import "./observability/nreactive";
import express from "express";
import { requestHandler, errorHandler } from "@nreactive/express";
import { captureException } from "@nreactive/core";
const app = express();
app.use(requestHandler({
scrubHeaders: ["authorization", "x-api-key", "x-session-token"],
scrubQueryParams: ["token", "secret"],
}));
app.use(express.json());
app.get("/boom", () => {
throw new Error("intentional test error");
});
app.get("/async-boom", async () => {
await Promise.reject(new Error("async test"));
});
app.get("/manual", (req, res) => {
try {
doWork();
res.json({ ok: true });
} catch (err) {
captureException(err, "error", { tags: { route: "/manual" } });
res.status(500).json({ ok: false });
}
});
app.use(errorHandler({
shouldCapture: (err) => !(err as any).expose, // skip `http-errors` you intentionally throw
}));
app.listen(3000);Links
- Core SDK:
@nreactive/core - Fastify adapter:
@nreactive/fastify - Full documentation: https://nreactive.com/docs
- Dashboard: https://nreactive.com/dashboard
License
PROPRIETARY. See LICENSE.
