NestJS (OpenTelemetry)
Instrument your NestJS application with OpenTelemetry and export traces and metrics to Traceway. No vendor-specific NestJS modules needed — OTel auto-instrumentation handles everything.
Installation
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/exporter-metrics-otlp-http \
@opentelemetry/apiIf you use Prisma, also install its OTel instrumentation for automatic query tracing:
npm install @prisma/instrumentationSetup
Create an instrumentation.ts file at the root of your NestJS project:
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { Resource } from "@opentelemetry/resources";
const sdk = new NodeSDK({
resource: new Resource({
"service.name": "my-nestjs-app",
"service.version": "1.0.0",
}),
traceExporter: new OTLPTraceExporter({
url: "https://your-traceway-instance.com/api/otel/v1/traces",
headers: { Authorization: "Bearer your-project-token" },
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: "https://your-traceway-instance.com/api/otel/v1/metrics",
headers: { Authorization: "Bearer your-project-token" },
}),
exportIntervalMillis: 30_000,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();If you use Prisma, add
import { PrismaInstrumentation } from "@prisma/instrumentation"and includenew PrismaInstrumentation()in theinstrumentationsarray alongsidegetNodeAutoInstrumentations().
Running Your App
The instrumentation file must load before NestJS boots. Use the --require flag:
# Development (with ts-node)
node --require ts-node/register --require ./instrumentation.ts src/main.ts
# Production (compiled JS)
node --require ./instrumentation.js dist/main.jsAdd these as scripts in package.json:
{
"scripts": {
"start": "node --require ./instrumentation.js dist/main.js",
"start:dev": "node --require ts-node/register --require ./instrumentation.ts src/main.ts"
}
}If you use the NestJS CLI (nest start), set NODE_OPTIONS in your environment:
export NODE_OPTIONS="--require ./instrumentation.js"
npm run startWhat Gets Captured
NestJS uses Express (or Fastify) under the hood — both are CJS packages that OTel auto-instrumentation patches successfully. The following is captured automatically:
| Layer | What's Captured |
|---|---|
| HTTP (Express/Fastify) | Incoming requests with route, method, status code, duration |
| NestJS handlers | Controller method spans via instrumentation-nestjs-core |
http.route | Parameterized routes (e.g., GET /api/users/:id) — set automatically |
| Exceptions | Thrown errors recorded as Issues with stack traces |
Prisma (@prisma/instrumentation) | Query spans with operation name, model, duration |
Database (pg, mysql2, mongodb) | Query spans with SQL statements |
Cache (redis, ioredis) | Cache operation spans |
Outgoing fetch() | Outgoing HTTP request spans (auto via instrumentation-undici) |
Outgoing HTTP (axios) | Outgoing request spans |
| DNS | DNS lookup spans |
Root SERVER spans become Endpoints in Traceway, child spans become Spans, and exception events become Issues.
Why NestJS doesn't need a route helper
Unlike Hono and Next.js, NestJS uses Express (or Fastify) internally. The @opentelemetry/instrumentation-express package automatically sets http.route on the root HTTP span. Endpoint grouping works out of the box — GET /api/users/1 and GET /api/users/2 are grouped as GET /api/users/:id without any extra code.
Prisma Auto-Instrumentation
With @prisma/instrumentation registered in the SDK, every Prisma query automatically creates child spans:
// Just use Prisma normally — spans are created automatically
const users = await this.prismaService.user.findMany();
const user = await this.prismaService.user.create({ data: { name, email } });The resulting trace in Traceway:
GET /api/users ← Endpoint
└─ prisma:client:query ← Span (findMany on User)Environment Variable Alternative
Instead of hardcoding the endpoint and token, you can use standard OTel environment variables:
export OTEL_SERVICE_NAME="my-nestjs-app"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://your-traceway-instance.com/api/otel"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer your-project-token"Test Your Integration
Add a test endpoint:
import { Controller, Get } from "@nestjs/common";
@Controller("api")
export class AppController {
@Get("test-error")
testError() {
throw new Error("Test error from NestJS");
}
}Visit /api/test-error and check your Traceway dashboard — the error should appear under Issues with a full stack trace.
Next Steps
- Custom Instrumentation — manual spans, decorators, and exception capture
- Node.js OTel Guide — general Node.js setup details
- OTel Trace Mapping — how OTel spans map to Traceway concepts