@nreactive/nestjs
v0.2.0
Published
NestJS module + interceptor + exception filter for @nreactive/core — automatic request-context propagation and error capture, works against the Express or Fastify HTTP adapter
Maintainers
Readme
@nreactive/nestjs
NestJS module + interceptor + exception filter for @nreactive/core — automatic request-context propagation and error capture for the nreactive AI error-fix pipeline.
- One-line install via
NreactiveModule.forRoot()— registers both pieces globally viaAPP_INTERCEPTOR+APP_FILTER. - Wraps every request in an
AsyncLocalStoragecontext — method, url, request ID, scrubbed headers, route handler name, andreq.userare attached to any error captured during handling. - Captures every error a controller can throw — synchronous throws, rejected Promises, errored Observables — by extending NestJS's own
BaseExceptionFilter(so the HTTP response shape stays identical to what NestJS would have sent). - Works against the Express and Fastify HTTP adapters NestJS supports out of the box, with no per-adapter branching.
- Zero runtime dependencies of its own — just
@nreactive/core,@nestjs/common,@nestjs/core, andrxjs(all peers). - Works with NestJS 10 and 11. Node ≥ 20.
Install
npm install @nreactive/core @nreactive/nestjs
# or: pnpm add @nreactive/core @nreactive/nestjs
# or: yarn add @nreactive/core @nreactive/nestjs
# or: bun add @nreactive/core @nreactive/nestjsAI-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/nestjsSDKs to this NestJS 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/nestjswith that package manager.- Create a file
src/observability/nreactive.tsthat 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
src/main.ts, addimport "./observability/nreactive";as the very first import, beforeimport { NestFactory } from "@nestjs/core";and before any other framework or app import.- In
src/app.module.ts, importNreactiveModulefrom@nreactive/nestjsand addNreactiveModule.forRoot()to theimportsarray ofAppModule. The module wires both the global interceptor (request context) and the global exception filter (error capture) viaAPP_INTERCEPTORandAPP_FILTERprovider tokens — no further wiring inmain.tsis needed.- 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 controller (e.g.
@Get("boom") boom() { throw new Error("nreactive test"); }) 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 NestJS, or if it uses a custom HTTP adapter other than Express or Fastify).
Quick start
// src/observability/nreactive.ts — imported FIRST in main.ts
import { init } from "@nreactive/core";
init({
appId: process.env.NREACTIVE_APP_ID!,
environment: process.env.NODE_ENV,
release: process.env.APP_VERSION,
});// src/main.ts
import "./observability/nreactive"; // ← must be the first import
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();// src/app.module.ts
import { Module } from "@nestjs/common";
import { NreactiveModule } from "@nreactive/nestjs";
import { AppController } from "./app.controller";
@Module({
imports: [NreactiveModule.forRoot()],
controllers: [AppController],
})
export class AppModule {}Get your App ID from the nreactive dashboard.
How it works
NreactiveInterceptoris registered globally via theAPP_INTERCEPTORprovider. For every incoming HTTP request it pulls the underlying request fromExecutionContext.switchToHttp().getRequest(), extracts method/url/requestId/scrubbed headers/user, and opens a freshAsyncLocalStorageframe around the handler's Observable subscription. AnycaptureExceptioncall inside that subtree — including fromawaited work and downstream operators — automatically picks up the request context. Microservice / WebSocket / GraphQL contexts (no HTTP request) are passed through unchanged.NreactiveExceptionFilteris registered globally via theAPP_FILTERprovider. It extends NestJS's ownBaseExceptionFilter, captures the exception viagetClient().captureException(err, classify(err)), then callssuper.catch(err, host)so the HTTP response shape (status code, body) is exactly what NestJS would have sent without the filter. Works forHttpExceptionand anyErrorsubclass.NreactiveModule.forRoot()is a one-line installation — both pieces are wired with the supplied options bag via theNREACTIVE_OPTIONSinjection token. No call toapp.useGlobalInterceptors(...)orapp.useGlobalFilters(...)frommain.tsis required.
Request IDs are sourced in this order:
req.id(Fastify provides this; some Express middleware sets it too)X-Request-IDheaderX-Correlation-IDheader- Generated
randomUUID()
API
NreactiveModule.forRoot(options?)
interface NreactiveModuleOptions {
/** 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;
/**
* Decide whether to capture a given exception. Default: always true.
* Useful for filtering out HttpException with sub-500 status codes.
*/
shouldCapture?: (err: unknown) => boolean;
}Returns a DynamicModule you add to the imports array of your root AppModule.
NreactiveInterceptor
A standalone NestInterceptor exported for users who want to register the interceptor manually:
import { NestFactory } from "@nestjs/core";
import { NreactiveInterceptor } from "@nreactive/nestjs";
import { AppModule } from "./app.module";
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new NreactiveInterceptor({ breadcrumbs: false }));NreactiveExceptionFilter
A standalone ExceptionFilter exported for the same reason. Standalone use needs the HttpAdapterHost to construct the underlying BaseExceptionFilter:
import { HttpAdapterHost } from "@nestjs/core";
import { NreactiveExceptionFilter } from "@nreactive/nestjs";
const httpAdapterHost = app.get(HttpAdapterHost);
app.useGlobalFilters(new NreactiveExceptionFilter(httpAdapterHost));Attaching user context
If your auth guard/middleware attaches req.user = { id, email, username }, the interceptor picks it up automatically and tags every error with that user.
To attach user info yourself (e.g. after loading from a session in a guard), import addContext from @nreactive/core:
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { addContext } from "@nreactive/core";
@Injectable()
export class SessionGuard implements CanActivate {
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const session = await loadSession(req);
if (session) addContext({ user: { id: session.userId, email: session.email } });
return Boolean(session);
}
}Because the interceptor opens the AsyncLocalStorage frame before the guard chain runs (interceptors wrap guards in NestJS), addContext from inside a guard merges into the active request context and propagates to any error captured later in the request.
Full example
// src/main.ts
import "./observability/nreactive";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();// src/app.module.ts
import { HttpException } from "@nestjs/common";
import { Module } from "@nestjs/common";
import { NreactiveModule } from "@nreactive/nestjs";
import { AppController } from "./app.controller";
@Module({
imports: [
NreactiveModule.forRoot({
scrubHeaders: ["authorization", "x-api-key", "x-session-token"],
scrubQueryParams: ["token", "secret"],
// Don't capture client errors (4xx) — only server faults (5xx).
shouldCapture: (err) =>
!(err instanceof HttpException && err.getStatus() < 500),
}),
],
controllers: [AppController],
})
export class AppModule {}// src/app.controller.ts
import { Controller, Get } from "@nestjs/common";
import { captureException } from "@nreactive/core";
@Controller()
export class AppController {
@Get("/")
hello() {
return { hello: "world" };
}
@Get("/boom")
boom() {
throw new Error("intentional test error");
}
@Get("/async-boom")
async asyncBoom() {
await Promise.reject(new Error("async test"));
}
@Get("/manual")
manual() {
try {
doWork();
return { ok: true };
} catch (err) {
captureException(err, "error", { tags: { route: "/manual" } });
throw err;
}
}
}Links
- Core SDK:
@nreactive/core - Express adapter:
@nreactive/express - Fastify adapter:
@nreactive/fastify - Full documentation: https://nreactive.com/docs
- Dashboard: https://nreactive.com/dashboard
License
PROPRIETARY. See LICENSE.
