@nreactive/fastify
v0.2.0
Published
Fastify plugin for @nreactive/core — automatic request-context propagation and error capture
Maintainers
Readme
@nreactive/fastify
Fastify 4/5 plugin for @nreactive/core — automatic request-context propagation and error capture for the nreactive AI error-fix pipeline.
- Registers
onRequest+onErrorhooks that attach method, url, request ID, route, and scrubbed headers to any error captured during handling. - Uses Fastify's built-in async context propagation — no extra
async_hooksbookkeeping. - Zero runtime dependencies of its own — just
@nreactive/core(peer) and Fastify. - Works with Fastify 4 and 5. Node ≥ 20.
Install
npm install @nreactive/core @nreactive/fastify
# or: pnpm add @nreactive/core @nreactive/fastify
# or: yarn add @nreactive/core @nreactive/fastify
# or: bun add @nreactive/core @nreactive/fastifyAI-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/fastifySDKs to this Fastify 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/fastifywith 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 includingfastify.- In the Fastify app setup, register the plugin as early as possible:
await app.register((await import("@nreactive/fastify")).default). Do this before registering user routes so theonRequesthook fires for every request.- 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 Fastify).
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 Fastify from "fastify";
import nreactivePlugin from "@nreactive/fastify";
const app = Fastify({ logger: true });
await app.register(nreactivePlugin); // register BEFORE routes
app.get("/", async () => ({ hello: "world" }));
await app.listen({ port: 3000 });Get your App ID from the nreactive dashboard.
How it works
onRequesthook — fires very early in Fastify's lifecycle and callsaddContext({...})so anycaptureExceptiontriggered during handling automatically includes:method(uppercased)url(with query params scrubbed)requestId(Fastify'sreq.id, elseX-Request-IDheader, elserandomUUID())route(fromreq.routeOptions.urlorreq.routerPath)headers(scrubbed)
onErrorhook — captures every error Fastify emits from a route or plugin, classifies severity via@nreactive/core, and forwards it to the ingest pipeline.
API
Default export — the plugin
import nreactivePlugin from "@nreactive/fastify";
await app.register(nreactivePlugin, {
// Header names to redact. Merged with @nreactive/core defaults.
scrubHeaders: ["authorization", "x-api-key"],
// Query param names to redact in the URL. Merged with core defaults.
scrubQueryParams: ["token", "secret"],
// Emit an `http.server` breadcrumb for each request. Default: true.
breadcrumbs: true,
});Options type:
interface NreactivePluginOptions {
scrubHeaders?: string[];
scrubQueryParams?: string[];
breadcrumbs?: boolean;
}A named export plugin is also provided for CJS consumers that prefer destructuring:
const { plugin } = require("@nreactive/fastify");Attaching user context
Fastify doesn't put auth info on req.user by convention, so you call addContext yourself in a hook or route:
import { addContext } from "@nreactive/core";
app.addHook("preHandler", async (req) => {
const user = await resolveUser(req);
if (user) addContext({ user: { id: user.id, email: user.email } });
});Because @nreactive/fastify registers its onRequest hook first, your preHandler runs inside the context frame it opened — the user info merges into the active store, not a detached one.
Full example
import "./observability/nreactive";
import Fastify from "fastify";
import nreactivePlugin from "@nreactive/fastify";
import { captureException, addContext } from "@nreactive/core";
const app = Fastify({ logger: true });
await app.register(nreactivePlugin, {
scrubHeaders: ["authorization", "x-session-token"],
});
app.addHook("preHandler", async (req) => {
const session = await getSession(req);
if (session) addContext({ user: { id: session.userId } });
});
app.get("/boom", async () => {
throw new Error("intentional test error");
});
app.get("/manual", async (req, reply) => {
try {
doWork();
return { ok: true };
} catch (err) {
captureException(err, "error", { tags: { route: "/manual" } });
reply.code(500);
return { ok: false };
}
});
await app.listen({ port: 3000 });Links
- Core SDK:
@nreactive/core - Express adapter:
@nreactive/express - Full documentation: https://nreactive.com/docs
- Dashboard: https://nreactive.com/dashboard
License
PROPRIETARY. See LICENSE.
