- name
- sentry-nestjs-sdk
- description
- Full Sentry SDK setup for NestJS. Use when asked to "add Sentry to NestJS", "install @sentry/nestjs", "setup Sentry in NestJS", or configure error monitoring, tracing, profiling, logging, metrics, crons, or AI monitoring for NestJS applications. Supports Express and Fastify adapters, GraphQL, microservices, WebSockets, and background jobs.
- license
- Apache-2.0
- category
- sdk-setup
- parent
- sentry-sdk-setup
- disable-model-invocation
- true
> [All Skills](../../SKILL_TREE.md) > [SDK Setup](../sentry-sdk-setup/SKILL.md) > NestJS SDK
# Sentry NestJS SDK
Opinionated wizard that scans your NestJS project and guides you through complete Sentry setup.
## Invoke This Skill When
- User asks to "add Sentry to NestJS" or "setup Sentry" in a NestJS app
- User wants error monitoring, tracing, profiling, logging, metrics, or crons in NestJS
- User mentions `@sentry/nestjs` or Sentry + NestJS
- User wants to monitor NestJS controllers, services, guards, microservices, or background jobs
> **Note:** SDK versions and APIs below reflect `@sentry/nestjs` 10.x (NestJS 8–11 supported).
> Always verify against [docs.sentry.io/platforms/node/guides/nestjs/](https://docs.sentry.io/platforms/node/guides/nestjs/) before implementing.
---
## Phase 1: Detect
Run these commands to understand the project before making recommendations:
```bash
# Confirm NestJS project
grep -E '"@nestjs/core"' package.json 2>/dev/null
# Check NestJS version
node -e "console.log(require('./node_modules/@nestjs/core/package.json').version)" 2>/dev/null
# Check existing Sentry
grep -i sentry package.json 2>/dev/null
ls src/instrument.ts 2>/dev/null
grep -r "Sentry.init\|@sentry" src/main.ts src/instrument.ts 2>/dev/null
# Check for existing Sentry DI wrapper (common in enterprise NestJS)
grep -rE "SENTRY.*TOKEN|SentryProxy|SentryService" src/ libs/ 2>/dev/null
# Check for config-class-based init (vs env-var-based)
grep -rE "class SentryConfig|SentryConfig" src/ libs/ 2>/dev/null
# Check if SentryModule.forRoot() is already registered in a shared module
grep -rE "SentryModule\.forRoot|SentryProxyModule" src/ libs/ 2>/dev/null
# Detect HTTP adapter (default is Express)
grep -E "FastifyAdapter|@nestjs/platform-fastify" package.json src/main.ts 2>/dev/null
# Detect GraphQL
grep -E '"@nestjs/graphql"|"apollo-server"' package.json 2>/dev/null
# Detect microservices
grep '"@nestjs/microservices"' package.json 2>/dev/null
# Detect WebSockets
grep -E '"@nestjs/websockets"|"socket.io"' package.json 2>/dev/null
# Detect task queues / scheduled jobs
grep -E '"@nestjs/bull"|"@nestjs/bullmq"|"@nestjs/schedule"|"bullmq"|"bull"' package.json 2>/dev/null
# Detect databases
grep -E '"@prisma/client"|"typeorm"|"mongoose"|"pg"|"mysql2"' package.json 2>/dev/null
# Detect AI libraries
grep -E '"openai"|"@anthropic-ai"|"langchain"|"@langchain"|"@google/generative-ai"|"ai"' package.json 2>/dev/null
# Check for companion frontend
ls -d ../frontend ../web ../client ../ui 2>/dev/null
```
**What to note:**
- Is `@sentry/nestjs` already installed? If yes, check if `instrument.ts` exists and `Sentry.init()` is called — may just need feature config.
- **Sentry DI wrapper detected?** → The project wraps Sentry behind a DI token (e.g. `SENTRY_PROXY_TOKEN`) for testability. Use the injected proxy for all runtime Sentry calls (`startSpan`, `captureException`, `withIsolationScope`) instead of importing `@sentry/nestjs` directly in controllers, services, and processors. Only `instrument.ts` should import `@sentry/nestjs` directly.
- **Config class detected?** → The project uses a typed config class for `Sentry.init()` options (e.g. loaded from YAML or `@nestjs/config`). Any new SDK options must be added to the config type — do not hardcode values that should be configurable per environment.
- **`SentryModule.forRoot()` already registered?** → If it's in a shared module (e.g. a Sentry proxy module), do not add it again in `AppModule` — this causes duplicate interceptor registration.
- Express (default) or Fastify adapter? Express is fully supported; Fastify works but has known edge cases.
- GraphQL detected? → `SentryGlobalFilter` handles it natively.
- Microservices detected? → Recommend RPC exception filter.
- Task queues / `@nestjs/schedule`? → Recommend crons.
- AI libraries? → Auto-instrumented, zero config.
- Prisma? → Requires manual `prismaIntegration()`.
- Companion frontend? → Triggers Phase 4 cross-link.
---
## Phase 2: Recommend
Based on what you found, present a concrete proposal. Don't ask open-ended questions — lead with a recommendation:
**Always recommended (core coverage):**
- ✅ **Error Monitoring** — captures unhandled exceptions across HTTP, GraphQL, RPC, and WebSocket contexts
- ✅ **Tracing** — auto-instruments middleware, guards, pipes, interceptors, filters, and route handlers
**Recommend when detected:**
- ✅ **Profiling** — production apps where CPU performance matters (`@sentry/profiling-node`)
- ✅ **Logging** — structured Sentry Logs + optional console capture
- ✅ **Crons** — `@nestjs/schedule`, Bull, or BullMQ detected
- ✅ **Metrics** — business KPIs or SLO tracking
- ✅ **AI Monitoring** — OpenAI/Anthropic/LangChain/etc. detected (auto-instrumented, zero config)
**Recommendation matrix:**
| Feature | Recommend when... | Reference |
| ---------------- | -------------------------------------------------- | ---------------------------------------------- |
| Error Monitoring | **Always** — non-negotiable baseline | `${SKILL_ROOT}/references/error-monitoring.md` |
| Tracing | **Always** — NestJS lifecycle is auto-instrumented | `${SKILL_ROOT}/references/tracing.md` |
| Profiling | Production + CPU-sensitive workloads | `${SKILL_ROOT}/references/profiling.md` |
| Logging | Always; enhanced for structured log aggregation | `${SKILL_ROOT}/references/logging.md` |
| Metrics | Custom business KPIs or SLO tracking | `${SKILL_ROOT}/references/metrics.md` |
| Crons | `@nestjs/schedule`, Bull, or BullMQ detected | `${SKILL_ROOT}/references/crons.md` |
| AI Monitoring | OpenAI/Anthropic/LangChain/etc. detected | `${SKILL_ROOT}/references/ai-monitoring.md` |
Propose: _"I recommend Error Monitoring + Tracing + Logging. Want Profiling, Crons, or AI Monitoring too?"_
---
## Phase 3: Guide
### Install
```bash
# Core SDK (always required — includes @sentry/node)
npm install @sentry/nestjs
# With profiling support (optional)
npm install @sentry/nestjs @sentry/profiling-node
```
> ⚠️ **Do NOT install `@sentry/node` alongside `@sentry/nestjs`** — `@sentry/nestjs` re-exports everything from `@sentry/node`. Installing both causes duplicate registration.
### Three-File Setup (Required)
NestJS requires a specific three-file initialization pattern because the Sentry SDK must patch Node.js modules (via OpenTelemetry) **before** NestJS loads them.
> **Before creating new files**, check Phase 1 results:
>
> - If `instrument.ts` already exists → modify it, don't create a new one.
> - If a config class drives `Sentry.init()` → read options from the config instead of hardcoding env vars.
> - If a Sentry DI wrapper exists → use it for runtime calls instead of importing `@sentry/nestjs` directly in services/controllers.
#### Step 1: Create `src/instrument.ts`
```typescript
import * as Sentry from "@sentry/nestjs";
// Optional: add profiling
// import { nodeProfilingIntegration } from "@sentry/profiling-node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.SENTRY_ENVIRONMENT ?? "production",
release: process.env.SENTRY_RELEASE,
// Data collection (SDK ≥ 10.57.0 — replaces deprecated sendDefaultPii)
dataCollection: {
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
// https://docs.sentry.io/platforms/javascript/guides/nestjs/configuration/options/#dataCollection
// userInfo: false,
// httpBodies: [],
},
// Tracing — lower to 0.1–0.2 in high-traffic production
tracesSampleRate: 1.0,
// Profiling (requires @sentry/profiling-node)
// integrations: [nodeProfilingIntegration()],
// profileSessionSampleRate: 1.0,
// profileLifecycle: "trace",
// Structured logs (SDK ≥ 9.41.0)
enableLogs: true,
});
```
**Config-driven `Sentry.init()`:** If Phase 1 found a typed config class (e.g. `SentryConfig`), read options from it instead of using raw `process.env`. This is common in NestJS apps that use `@nestjs/config` or custom config loaders:
```typescript
import * as Sentry from "@sentry/nestjs";
import { loadConfiguration } from "./config";
const config = loadConfiguration();
Sentry.init({
dsn: config.sentry.dsn,
environment: config.sentry.environment ?? "production",
release: config.sentry.release,
dataCollection: config.sentry.dataCollection ?? {
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
// https://docs.sentry.io/platforms/javascript/guides/nestjs/configuration/options/#dataCollection
// userInfo: false,
// httpBodies: [],
},
tracesSampleRate: config.sentry.tracesSampleRate ?? 1.0,
profileSessionSampleRate: config.sentry.profilesSampleRate ?? 1.0,
profileLifecycle: "trace",
enableLogs: true,
});
```
When adding new SDK options (e.g. `dataCollection`, `profileSessionSampleRate`), add them to the config type so they can be configured per environment.
#### Step 2: Import `instrument.ts` FIRST in `src/main.ts`
```typescript
// instrument.ts MUST be the very first import — before NestJS or any other module
import "./instrument";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable graceful shutdown — flushes Sentry events on SIGTERM/SIGINT
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();
```
> **Why first?** OpenTelemetry must monkey-patch `http`, `express`, database drivers, and other modules before they load. Any module that loads before `instrument.ts` will not be auto-instrumented.
#### Step 3: Register `SentryModule` and `SentryGlobalFilter` in `src/app.module.ts`
```typescript
import { Module } from "@nestjs/common";
import { APP_FILTER } from "@nestjs/core";
import { SentryModule, SentryGlobalFilter } from "@sentry/nestjs/setup";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [
SentryModule.forRoot(), // Registers SentryTracingInterceptor globally
],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_FILTER,
useClass: SentryGlobalFilter, // Captures all unhandled exceptions
},
],
})
export class AppModule {}
```
**What each piece does:**
- `SentryModule.forRoot()` — registers `SentryTracingInterceptor` as a global `APP_INTERCEPTOR`, enabling HTTP transaction naming
- `SentryGlobalFilter` — extends `BaseExceptionFilter`; captures exceptions across HTTP, GraphQL (rethrows `HttpException` without reporting), and RPC contexts
> ⚠️ **Do NOT register `SentryModule.forRoot()` twice.** If Phase 1 found it already imported in a shared library module (e.g. a `SentryProxyModule` or `AnalyticsModule`), do not add it again in `AppModule`. Duplicate registration causes every span to be intercepted twice, bloating trace data.
> ⚠️ **Two entrypoints, different imports:**
>
> - `@sentry/nestjs` → SDK init, capture APIs, decorators (`SentryTraced`, `SentryCron`, `SentryExceptionCaptured`)
> - `@sentry/nestjs/setup` → NestJS DI constructs (`SentryModule`, `SentryGlobalFilter`)
>
> Never import `SentryModule` from `@sentry/nestjs` (main entrypoint) — it loads `@nestjs/common` before OpenTelemetry patches it, breaking auto-instrumentation.
### ESM Setup (Node ≥ 18.19.0)
For ESM applications, use `--import` instead of a file import:
```javascript
// instrument.mjs
import * as Sentry from "@sentry/nestjs";
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
});
```
```json
// package.json
{
"scripts": {
"start": "node --import ./instrument.mjs -r ts-node/register src/main.ts"
}
}
```
Or via environment:
```bash
NODE_OPTIONS="--import ./instrument.mjs" npm run start
```
### Exception Filter Options
Choose the approach that fits your existing architecture:
#### Option A: No existing global filter — use `SentryGlobalFilter` (recommended)
Already covered in Step 3 above. This is the simplest option.
#### Option B: Existing custom global filter — add `@SentryExceptionCaptured()` decorator
```typescript
import { Catch, ExceptionFilter, ArgumentsHost } from "@nestjs/common";
import { SentryExceptionCaptured } from "@sentry/nestjs";
@Catch()
export class YourExistingFilter implements ExceptionFilter {
@SentryExceptionCaptured() // Wraps catch() to auto-report exceptions
catch(exception: unknown, host: ArgumentsHost): void {
// Your existing error handling continues unchanged
}
}
```
View on GitHub