NestJS (OTel)
Quick Start

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/api

If you use Prisma, also install its OTel instrumentation for automatic query tracing:

npm install @prisma/instrumentation

Setup

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 include new PrismaInstrumentation() in the instrumentations array alongside getNodeAutoInstrumentations().

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.js

Add 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 start

What 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:

LayerWhat's Captured
HTTP (Express/Fastify)Incoming requests with route, method, status code, duration
NestJS handlersController method spans via instrumentation-nestjs-core
http.routeParameterized routes (e.g., GET /api/users/:id) — set automatically
ExceptionsThrown 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
DNSDNS 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