Let AI set up TraceKit for you
AI assistants can guide you through the entire setup process
Node.js / TypeScript Integration Guide
Learn how to instrument your Node.js and TypeScript applications with TraceKit APM for zero-config distributed tracing.
Using Express?
See our dedicated Express observability guide for framework-specific setup, pain points, and best practices.
Node.js Distributed Tracing Guide
Go deeper with our Node.js distributed tracing guide -- covering common pain points, production patterns, and code examples.
New: Frontend Observability
Looking for browser-side error tracking? Check out the new Browser SDK with React, Vue, Angular, Next.js, and Nuxt integrations via Framework Wrappers.
Zero-Config Automatic Tracing!
TraceKit Node APM package automatically traces HTTP requests, errors, and provides first-class TypeScript support. Just install, configure, and go!
Prerequisites
- • Node.js 16.x or higher
- • Express 4.x/5.x or NestJS 10.x
- • A TraceKit account (create one free)
- • A generated API key from the API Keys page
What Gets Traced Automatically?
With TraceKit Node APM installed, these operations are traced automatically:
| Component | Span Type | Captured Attributes | Auto-Traced? |
|---|---|---|---|
| Incoming HTTP | SERVER | route, method, status, duration, client_ip, user_agent | Yes |
| Outgoing HTTP | CLIENT | method, url, status_code, duration, peer.service | Yes |
| PostgreSQL | DB | db.system, db.statement, db.name, duration | Yes |
| MySQL | DB | db.system, db.statement, db.name, duration | Yes |
| MongoDB | DB | db.system (mongodb), db.operation, db.name, duration | Yes |
| Redis | DB | db.system (redis), db.statement, duration | Yes |
| LLM (OpenAI/Anthropic) | CLIENT | gen_ai.system, model, tokens, cost, finish_reason, latency | Yes |
| Exceptions | N/A | type, message, stack_trace, request_context | Yes |
| Custom Spans | Custom | user-defined attributes | Manual |
Auto-Instrumented Libraries
The following libraries are automatically instrumented when detected. Import them after calling tracekit.init() for automatic span creation:
| Library | Span Type | Captured Attributes | Auto-Instrumented? |
|---|---|---|---|
| pg (PostgreSQL) | DB | db.system, db.statement, db.name, duration | ✓ Yes |
| mysql / mysql2 | DB | db.system, db.statement, db.name, duration | ✓ Yes |
| mongodb | DB | db.system (mongodb), db.operation, db.name, duration | ✓ Yes |
| ioredis / redis | DB | db.system (redis), db.statement, duration | ✓ Yes |
| node:http / node:https | CLIENT | http.method, http.url, http.status_code, peer.service | ✓ Yes |
| undici (Fetch API) | CLIENT | http.method, http.url, http.status_code, peer.service | ✓ Yes |
Note: Import database and HTTP client libraries after calling tracekit.init() to ensure automatic instrumentation is applied.
Installation
Install the TraceKit Node APM package via npm or yarn:
npm install @tracekit/node-apm # or yarn add @tracekit/node-apmExpress Setup
JavaScript
const express = require('express');
const tracekit = require('@tracekit/node-apm');
const app = express();
// Initialize TraceKit (before routes!)
tracekit.init({
apiKey: process.env.TRACEKIT_API_KEY,
serviceName: 'my-express-app',
endpoint: 'https://app.tracekit.dev/v1/traces',
enableCodeMonitoring: true, // Optional: Enable live debugging
});
// Add TraceKit middleware (before routes!)
app.use(tracekit.middleware());
// Your routes - automatically traced!
app.get('/api/users', (req, res) => {
res.json({ users: ['alice', 'bob'] });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});TypeScript
import express from 'express';
import * as tracekit from '@tracekit/node-apm';
const app = express();
// Initialize TraceKit (before routes!)
tracekit.init({
apiKey: process.env.TRACEKIT_API_KEY!,
serviceName: 'my-express-app',
endpoint: 'https://app.tracekit.dev/v1/traces',
enableCodeMonitoring: true, // Optional: Enable live debugging
});
// Add TraceKit middleware (before routes!)
app.use(tracekit.middleware());
// Your routes - automatically traced!
app.get('/api/users', (req, res) => {
res.json({ users: ['alice', 'bob'] });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Verify It Works
Start your application and make a few requests. Then open the Traces page in your TraceKit dashboard. You should see:
- • SERVER spans for each incoming HTTP request with route and status
- • CLIENT spans for outgoing HTTP calls (auto-traced for http/https, fetch, axios)
- • DB spans for database queries (auto-traced for PostgreSQL, MySQL, MongoDB, Redis)
Traces typically appear within 30 seconds. If you don't see them, check the Troubleshooting section.
NestJS Setup
1. Import TracekitModule
Add the TracekitModule to your app.module.ts:
// app.module.ts
import { Module } from '@nestjs/common';
import { TracekitModule } from '@tracekit/node-apm/nestjs';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TracekitModule.forRoot({
apiKey: process.env.TRACEKIT_API_KEY!,
serviceName: 'my-nestjs-app',
endpoint: 'https://app.tracekit.dev/v1/traces',
enableCodeMonitoring: true, // Optional: Enable live debugging
}),
UsersModule,
],
})
export class AppModule {}2. Async Configuration
For environment-based configuration using ConfigModule:
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TracekitModule } from '@tracekit/node-apm/nestjs';
@Module({
imports: [
ConfigModule.forRoot(),
TracekitModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
apiKey: config.get('TRACEKIT_API_KEY')!,
serviceName: config.get('APP_NAME', 'my-app'),
endpoint: config.get('TRACEKIT_ENDPOINT'),
enabled: config.get('NODE_ENV') === 'production',
enableCodeMonitoring: true, // Optional: Enable live debugging
}),
}),
],
})
export class AppModule {}Automatic Interceptor
The TracekitModule automatically registers a global interceptor that traces all HTTP requests. No additional middleware needed!
Middleware & Interceptor API
Framework integration functions and classes for automatic request tracing:
| Function / Class | Parameters | Returns | Description |
|---|---|---|---|
| createExpressMiddleware() | • client: TracekitClient• snapshot?: boolean |
RequestHandler | Creates Express middleware that automatically traces all incoming HTTP requests. Optionally enables snapshot capture on the request object. |
| getCurrentSpan() | • req: Request |
Span | undefined | Retrieves the current active span from an Express request. Returns undefined if no span is active. |
| getClientIp() | • req: Request |
string | undefined | Extracts client IP from request headers (X-Forwarded-For, X-Real-IP) with proxy support. |
| TracekitInterceptor | NestJS class | NestInterceptor | NestJS interceptor for automatic span creation on all controller methods. Registered globally by TracekitModule. |
Code Monitoring (Live Debugging)
Production-Safe Live Debugging
Capture variable state, stack traces, and request context at any point in your code without redeployment. Perfect for debugging production issues!
→ Full Code Monitoring DocumentationSetup
Enable code monitoring when initializing TraceKit:
import * as tracekit from '@tracekit/node-apm';
const client = tracekit.init({
apiKey: process.env.TRACEKIT_API_KEY!,
serviceName: 'my-app',
enableCodeMonitoring: true, // Enable live debugging
});Usage: Add Debug Points
Use client.captureSnapshot() to capture variable state:
app.post('/checkout', async (req, res) => {
const { userId, amount } = req.body;
// Capture snapshot at this point
await client.captureSnapshot('checkout-validation', {
userId,
amount,
timestamp: new Date().toISOString(),
});
// Your business logic...
const result = await processPayment(amount, userId);
// Another checkpoint
await client.captureSnapshot('payment-complete', {
userId,
paymentId: result.paymentId,
success: result.success,
});
res.json(result);
});Features
Auto-Registration
Breakpoints automatically created on first call
Variable Capture
Deep inspection of objects, arrays, and primitives
Stack Traces
Full call stack with file and line numbers
Request Context
HTTP method, route, headers, and query params
Production Safety
v6.0 adds multiple layers of protection so code monitoring is safe for production workloads:
PII Scrubbing
13 built-in sensitive-data patterns enabled by default. Matched values are replaced with typed [REDACTED:type] markers before data leaves the process.
Crash Isolation
Every capture entry point is wrapped in try/catch so a failure in the SDK never propagates to your application code.
Circuit Breaker
After 3 consecutive failures within 60 seconds the SDK enters a 5-minute cooldown, then auto-recovers. Fully configurable thresholds.
Remote Kill Switch
Toggle code monitoring on or off from the TraceKit dashboard. The change propagates to all connected SDK instances via SSE in real time.
Real-Time Updates via SSE
The Node SDK opens a lightweight Server-Sent Events connection to the TraceKit server on startup. Breakpoint enables/disables, kill-switch toggles, and configuration changes are pushed instantly — no polling delay. The SSE endpoint is auto-discovered from /sdk/snapshots/auto-register so no extra config is needed.
Exception Handling
Exceptions are automatically captured with full stack traces for code discovery:
// Exceptions in spans are automatically captured
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await getUserById(req.params.id);
res.json(user);
} catch (error) {
// TraceKit automatically captures exception with stack trace
next(error);
}
});
// Express error handler
app.use((err, req, res, next) => {
client.recordException(null, err);
res.status(500).json({ error: 'Internal Server Error' });
});Production Safe
Snapshots are sampled and have minimal performance impact. Capture rates and conditions can be controlled via the TraceKit dashboard.
Code Monitoring API
Programmatic API for code monitoring and snapshot capture:
| Method / Class | Parameters | Returns | Description |
|---|---|---|---|
| captureSnapshot() | • label: string — breakpoint label• vars: object — variables to capture |
Promise<void> | Captures a snapshot of variable state at the call site. Auto-registers a breakpoint on first call. Rate-limited to 1 capture/sec per label. |
| getSnapshotClient() | none | SnapshotClient | undefined | Returns the snapshot client if code monitoring is enabled, or undefined if disabled. Use to guard snapshot calls. |
| SnapshotClient | class | — | Advanced / Internal. Manages breakpoint polling and snapshot uploads. Created automatically when enableCodeMonitoring: true. |
| CaptureConfig | interface | — | Configuration for code monitoring capture behaviour. Fields: enablePiiScrubbing (boolean, default true), piiPatterns (PIIPatternEntry[]), circuitBreaker (CircuitBreakerConfig). |
| CircuitBreakerConfig | interface | — | Configures circuit breaker thresholds. Fields: maxFailures (number, default 3), windowMs (number, default 60000), cooldownMs (number, default 300000). |
| PIIPatternEntry | interface | — | Defines a sensitive-data pattern for PII scrubbing. Fields: name (string — pattern label), pattern (RegExp — detection regex), redactedType (string — marker used in [REDACTED:type]). |
End-to-End Workflow
Enable Code Monitoring
Code monitoring defaults to disabled in the Node.js SDK. You must explicitly enable it in your config.
const client = new TracekitClient({
apiKey: process.env.TRACEKIT_API_KEY,
serviceName: 'my-express-app',
enableCodeMonitoring: true, // default: false
});
Add Capture Points
Place captureSnapshot calls at code paths you want to debug. Note: captureSnapshot returns a Promise — use await.
app.post('/orders', async (req, res) => {
const order = req.body;
const user = req.user;
await client.captureSnapshot('order-processing', {
orderId: order.id,
total: order.total,
userId: user.id,
});
// Business logic continues...
res.json({ status: 'ok' });
});
Deploy and Verify Traces
Deploy your application and confirm traces are flowing. Check the TraceKit dashboard at /traces to see incoming requests.
# Start your Express app
node server.js
# Verify traces appear in dashboard at /traces
Navigate to Code Monitoring
Go to /snapshots and click the "Browse Code" tab. You'll see auto-discovered files and functions from your traces.
Set Breakpoints
Breakpoints are auto-registered on the first captureSnapshot call. You can also manually add breakpoints via the UI "Set Breakpoint" button.
Trigger a Capture
Make a request that hits a code path with captureSnapshot. The SDK uses new Error().stack for file/line detection and AsyncLocalStorage for request context.
View Snapshot
Go to /snapshots and click the captured snapshot. View the captured variables, call stack, request context, security flags, and trace link.
LLM Instrumentation
TraceKit automatically instruments OpenAI and Anthropic SDK calls when detected. LLM calls appear as spans with OTel GenAI semantic convention attributes.
Zero-config auto-instrumentation
If openai or @anthropic-ai/sdk is installed, TraceKit patches them automatically at init. No code changes required.
Captured Attributes
| Attribute | Description |
|---|---|
| gen_ai.system | Provider name (openai, anthropic) |
| gen_ai.request.model | Model name (gpt-4o, claude-sonnet-4-20250514, etc.) |
| gen_ai.usage.input_tokens | Prompt token count |
| gen_ai.usage.output_tokens | Completion token count |
| gen_ai.response.finish_reasons | Finish reason (stop, end_turn, tool_calls) |
Configuration
const client = init({
apiKey: process.env.TRACEKIT_API_KEY,
serviceName: 'my-service',
endpoint: 'https://app.tracekit.dev/v1/traces',
// LLM instrumentation (all enabled by default)
instrumentLLM: {
enabled: true, // Master toggle
openai: true, // OpenAI instrumentation
anthropic: true, // Anthropic instrumentation
captureContent: false, // Capture prompts/completions (off by default)
},
});Environment Variable Override
Use TRACEKIT_LLM_CAPTURE_CONTENT=true to enable prompt/completion capture without code changes. Useful for enabling in staging but not production.
Streaming Support
Streaming responses produce a single span that covers the entire stream. Token counts are accumulated from the final chunk. No special configuration needed.
LLM Dashboard
View LLM cost, token usage, and latency metrics on the dedicated LLM Observability dashboard at /ai/llm in your TraceKit instance.
Local UI (Development Mode)
Debug Locally Without an Account
The Local UI lets you see traces in real-time on your local machine at http://localhost:9999. Perfect for development - no account signup or API key required!
Quick Start
1. Install the Local UI:
npm install -g @tracekit/local-ui2. Start the Local UI:
tracekit-local3. Run your app in development mode:
NODE_ENV=development node app.js4. Open your browser:
http://localhost:9999Automatic Detection
The SDK automatically detects when Local UI is running and sends traces to both your local instance and the cloud (if you have an API key configured). No code changes needed!
How It Works
Auto-Detection
SDK checks for Local UI at localhost:9999 on startup
Real-Time Updates
See traces instantly with WebSocket live updates
Development Only
Only activates when NODE_ENV=development
Works Offline
No internet connection required - everything runs locally
Benefits
- Zero friction onboarding - Try TraceKit without creating an account
- Faster debugging - No context switching to web dashboard
- Privacy first - Traces never leave your machine
- Perfect for demos - Show TraceKit without cloud dependency
Troubleshooting
If traces aren't appearing in Local UI, check:
- Local UI is running (
curl http://localhost:9999/api/health) NODE_ENV=developmentis set- SDK version is v0.3.2 or higher
- Check console for " Local UI detected" message
Service Discovery
TraceKit automatically instruments outgoing HTTP calls to create service dependency graphs. When your service makes an HTTP request to another service, TraceKit creates CLIENT spans and injects trace context headers.
Supported HTTP Clients
- http/https - Node.js built-in modules
- fetch - Node 18+ native fetch API
- axios - Works via http module
- node-fetch, got, superagent - Work via http module
Custom Service Name Mappings
For local development or when service names can't be inferred from hostnames, configure service name mappings:
import * as tracekit from '@tracekit/node-apm';
tracekit.init({
apiKey: process.env.TRACEKIT_API_KEY,
serviceName: 'my-service',
// Map localhost URLs to actual service names
serviceNameMappings: {
'localhost:8082': 'payment-service',
'localhost:8083': 'user-service',
'localhost:8084': 'inventory-service',
},
});
// Now requests to localhost:8082 will show as "payment-service"
const response = await fetch('http://localhost:8082/charge');
// -> Creates CLIENT span with peer.service = "payment-service"This maps localhost:8082 to "payment-service" in your service graph.
Service Name Detection
TraceKit intelligently extracts service names from URLs:
| URL | Extracted Service Name |
|---|---|
| http://payment-service:3000 | payment-service |
| http://payment.internal | payment |
| http://payment.svc.cluster.local | payment |
| https://api.example.com | api.example.com |
Viewing Service Dependencies
Visit your TraceKit dashboard to see the Service Map - a visual graph showing which services call which, with health metrics and latency data.
Manual Instrumentation (Optional)
Express - Manual Spans
import { getClient } from '@tracekit/node-apm';
app.post('/api/process', async (req, res) => {
const client = getClient();
const span = client.startSpan('process-data', null, {
'user.id': req.user?.id,
'data.size': req.body.items.length,
});
try {
const result = await processData(req.body);
client.endSpan(span, {
'result.count': result.length,
});
res.json(result);
} catch (error) {
client.recordException(span, error as Error);
client.endSpan(span, {}, 'ERROR');
throw error;
}
});NestJS - Manual Spans
import { Injectable, Inject } from '@nestjs/common';
import { TracekitClient } from '@tracekit/node-apm/nestjs';
@Injectable()
export class DataService {
constructor(
@Inject('TRACEKIT_CLIENT') private tracekit: TracekitClient
) {}
async processLargeDataset(data: any[]) {
const span = this.tracekit.startSpan('process-dataset', null, {
'dataset.size': data.length,
});
try {
const results = [];
for (const item of data) {
const result = await this.processItem(item);
results.push(result);
}
this.tracekit.endSpan(span, {
'results.count': results.length,
});
return results;
} catch (error) {
this.tracekit.recordException(span, error as Error);
this.tracekit.endSpan(span, {}, 'ERROR');
throw error;
}
}
}TracekitClient Tracing API
The TracekitClient provides these methods for manual span creation and management:
| Method | Parameters | Returns | Description |
|---|---|---|---|
| startTrace() | • op: string — operation name• attrs?: object — initial attributes |
Span | Creates a new root trace. Use for top-level operations not tied to an incoming HTTP request. |
| startServerSpan() | • op: string — operation name• attrs?: object — initial attributes |
Span | Creates a SERVER span for incoming requests. Extracts trace context from request headers for distributed tracing. |
| startSpan() | • op: string — operation name• parent?: Span — parent span (null for auto-link)• attrs?: object — initial attributes |
Span | Creates a child span. If parent is null, automatically links to the current active span from context. |
| endSpan() | • span: Span — the span to end• attrs?: object — final attributes• status?: string — "OK" or "ERROR" |
void | Ends a span, recording its duration. Optionally sets final attributes and status code. |
| addEvent() | • span: Span — target span• name: string — event name• attrs?: object — event attributes |
void | Records a timestamped event on a span. Use for significant occurrences during an operation (e.g., cache hit, retry). |
| recordException() | • span: Span — target span (or null)• error: Error — the exception |
void | Records an exception on a span with message, type, and stack trace. Automatically sets span status to ERROR. |
Lifecycle Methods
Control the SDK lifecycle and query its state:
| Method | Parameters | Returns | Description |
|---|---|---|---|
| flush() | none | Promise<void> | Forces an immediate flush of all pending spans to the backend. Call before process exit or after critical operations. |
| shutdown() | none | Promise<void> | Gracefully shuts down the SDK: flushes remaining data, releases resources, and stops background tasks. |
| isEnabled() | none | boolean | Returns whether the SDK is currently enabled and actively tracing requests. |
| shouldSample() | none | boolean | Returns whether the current request should be sampled based on the configured sample rate. |
| getTracer() | none | Tracer | Returns the underlying OpenTelemetry Tracer instance for advanced use cases requiring direct OTel API access. |
Complete Tracing Lifecycle Example
const client = getClient();
// Start a root trace for a background job or non-HTTP operation
const span = client.startTrace('process-order', { 'order.id': orderId });
try {
// Create a child span for a sub-operation
const childSpan = client.startSpan('validate-payment', span);
client.addEvent(childSpan, 'payment-validated', { amount: total });
client.endSpan(childSpan);
// End the root span with final attributes
client.endSpan(span, { 'order.status': 'completed' });
} catch (err) {
// Record the exception and mark the span as errored
client.recordException(span, err);
client.endSpan(span, {}, 'ERROR');
} finally {
// Ensure all spans are exported before continuing
await client.flush();
}
Configuration Options
Available configuration options:
tracekit.init({
// Required: Your TraceKit API key
apiKey: process.env.TRACEKIT_API_KEY,
// Optional: Service name (default: 'node-app')
serviceName: 'my-service',
// Optional: TraceKit endpoint
endpoint: 'https://app.tracekit.dev/v1/traces',
// Optional: Enable/disable tracing (default: true)
enabled: process.env.NODE_ENV !== 'development',
// Optional: Sample rate 0.0-1.0 (default: 1.0 = 100%)
sampleRate: 0.5, // Trace 50% of requests
// Optional: Enable code monitoring / live debugging (default: false)
enableCodeMonitoring: true,
});Environment Variables
Best practice: Store configuration in environment variables:
# .env
TRACEKIT_API_KEY=ctxio_your_generated_api_key_here
TRACEKIT_ENDPOINT=https://app.tracekit.dev/v1/traces
TRACEKIT_SERVICE_NAME=my-nodejs-app
TRACEKIT_CODE_MONITORING_ENABLED=true
NODE_ENV=productionImportant Security Note
Never commit your API key to version control. Use environment variables and keep your .env file out of git.
TracekitConfig Interface
The TracekitConfig interface accepts these options when calling init():
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| apiKey | string | Yes | — | Your TraceKit API key for authenticating with the collector |
| endpoint | string | No | "app.tracekit.dev" | TraceKit collector endpoint URL |
| tracesPath | string | No | "/v1/traces" | HTTP path for trace data export |
| metricsPath | string | No | "/v1/metrics" | HTTP path for metrics data export |
| serviceName | string | No | "node-app" | Name of your service shown in the trace dashboard and service map |
| enabled | boolean | No | true | Enable or disable tracing globally |
| sampleRate | number | No | 1.0 | Trace sampling rate from 0.0 to 1.0 (1.0 = trace 100%) |
| enableCodeMonitoring | boolean | No | false | Enable live code debugging and snapshot capture |
| autoInstrumentHttpClient | boolean | No | true | Auto-instrument outgoing HTTP calls via http/https and fetch |
| serviceNameMappings | Record<string, string> | No | {} | Map host:port patterns to friendly service names for the service map |
All configuration options available for the Node.js SDK:
| Option | Type | Default | Env Variable | Description |
|---|---|---|---|---|
| apiKey | string | required | TRACEKIT_API_KEY | Your TraceKit API key for authentication |
| endpoint | string | "app.tracekit.dev" | TRACEKIT_ENDPOINT | TraceKit collector endpoint URL |
| tracesPath | string | "/v1/traces" | TRACEKIT_TRACES_PATH | HTTP path for trace data export |
| metricsPath | string | "/v1/metrics" | TRACEKIT_METRICS_PATH | HTTP path for metrics data export |
| serviceName | string | "node-app" | TRACEKIT_SERVICE_NAME | Name of your service in the trace dashboard |
| enabled | boolean | true | TRACEKIT_ENABLED | Enable or disable tracing globally |
| sampleRate | number | 1.0 | TRACEKIT_SAMPLE_RATE | Trace sampling rate (0.0 to 1.0, where 1.0 = 100%) |
| enableCodeMonitoring | boolean | false | TRACEKIT_CODE_MONITORING_ENABLED | Enable live code debugging / snapshot capture |
| autoInstrumentHttpClient | boolean | true | - | Auto-instrument outgoing HTTP calls (http/https, fetch) |
| serviceNameMappings | Record<string, string> | {} | - | Map host:port to service names for service discovery |
Note: The Node.js SDK does not auto-read environment variables. Read them with process.env and pass to init().
Complete Example
Here's a complete Express + TypeScript example:
import express, { Request, Response } from 'express';
import * as tracekit from '@tracekit/node-apm';
const app = express();
app.use(express.json());
// Initialize TraceKit
tracekit.init({
apiKey: process.env.TRACEKIT_API_KEY!,
serviceName: 'express-api',
endpoint: 'https://app.tracekit.dev/v1/traces',
enabled: process.env.NODE_ENV === 'production',
enableCodeMonitoring: true, // Optional: Enable live debugging
});
// Add middleware
app.use(tracekit.middleware());
interface User {
id: string;
name: string;
email: string;
}
// Routes - automatically traced!
app.get('/api/users', (req: Request, res: Response) => {
const users: User[] = [
{ id: '1', name: 'Alice', email: '[email protected]' },
{ id: '2', name: 'Bob', email: '[email protected]' },
];
res.json(users);
});
// Manual span example
app.post('/api/users', async (req: Request, res: Response) => {
const client = tracekit.getClient();
const span = client.startSpan('create-user', null, {
'user.email': req.body.email,
});
try {
// Simulate user creation
const user: User = {
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
};
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 100));
client.endSpan(span, {
'user.id': user.id,
});
res.status(201).json(user);
} catch (error) {
client.recordException(span, error as Error);
client.endSpan(span, {}, 'ERROR');
res.status(500).json({ error: 'Failed to create user' });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log('Server running on port ' + PORT);
});You're all set!
Your Node.js application is now sending traces to TraceKit. Visit the Dashboard to see your traces.
Troubleshooting
Traces not appearing?
Cause: The SDK is not initialized or is initialized after route handlers are registered.
Fix:
-
•
Call
tracekit.init()BEFORE registering any Express/Fastify routes or middleware -
•
Verify API key with
console.log(process.env.TRACEKIT_API_KEY) -
•
Check that
enabledis not set tofalse - • Look for errors in the console output
Connection refused errors?
Cause: The TraceKit endpoint is unreachable from your Node.js process.
Fix:
-
•
Test with
curl -X POST https://app.tracekit.dev/v1/traces(expect 401) - • Check proxy/firewall settings
- • Verify the endpoint URL does not have a trailing slash
Code monitoring not working?
Cause: Code monitoring is disabled by default and requires explicit enablement.
Fix:
-
•
Set
enableCodeMonitoring: truein the init options -
•
Add
captureSnapshot()calls where you want to inspect variables - • Check the dashboard for active breakpoints
- • Allow up to 30 seconds for the SDK to poll for new breakpoints
Authentication errors (401/403)?
Cause: API key is wrong, has extra whitespace, or was revoked.
Fix:
-
•
Log the key to verify:
console.log(JSON.stringify(apiKey))(catches invisible characters) - • Regenerate in dashboard if needed
- • Ensure the correct key is used per environment
Partial or missing spans?
Cause: Auto-instrumentation is not capturing all libraries, or async context is lost.
Fix:
-
•
Verify
autoInstrumentHttpClient: true(default) for outgoing HTTP -
•
For database spans, ensure the database library is imported AFTER
tracekit.init() - • Check that async/await is used consistently (callbacks can lose trace context)
Custom Metrics
Track custom metrics like request counts, queue sizes, and response times using the TraceKit metrics API.
Counter
Track monotonically increasing values (requests, events):
import * as tracekit from '@tracekit/node-apm';
const client = tracekit.init({
apiKey: process.env.TRACEKIT_API_KEY,
serviceName: 'my-service',
});
// Create a counter with optional tags
const counter = client.counter('http.requests.total', { service: 'api' });
// Increment by 1
counter.inc();
// Add a specific value
counter.add(5);Gauge
Track values that can go up or down (queue size, connections):
// Create a gauge
const gauge = client.gauge('http.connections.active');
// Set to specific value
gauge.set(42);
// Increment/decrement
gauge.inc();
gauge.dec();Histogram
Track value distributions (latencies, sizes):
// Create a histogram with tags
const histogram = client.histogram('http.request.duration', { unit: 'ms' });
// Record values
histogram.record(45.2);
histogram.record(123.5);🔄 Migrating from OpenTelemetry
TraceKit wraps OpenTelemetry internally, so you get the same standards-based trace data with significantly less setup code. Here's how to migrate from a raw OpenTelemetry setup to TraceKit.
Before vs After
// 5 requires
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'my-service',
}),
traceExporter: new OTLPTraceExporter({
url: 'https://api.tracekit.io/v1/traces',
headers: { 'Authorization': 'Bearer ' + apiKey },
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());
// + Express middleware setup
// + manual span creation
const tracekit = require('tracekit');
tracekit.init({
apiKey: process.env.TRACEKIT_API_KEY,
serviceName: 'my-service',
});
// Express middleware: one line
app.use(tracekit.middleware());
Migration Steps
Install the SDK: npm install tracekit
Replace init code: Remove all @opentelemetry requires and SDK setup. Replace with tracekit.init(). Must be before route imports.
Replace middleware: Use app.use(tracekit.middleware()) for Express, or the framework-specific adapter for NestJS
Remove OTel packages: npm uninstall @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/auto-instrumentations-node
Verify: Start your app and check the Traces page for incoming data
Key Migration Benefits
- • 30 lines to 6 lines — no more boilerplate for exporters, resources, instrumentations
- • No OTel dependency management — TraceKit handles version pinning internally
- • Built-in code monitoring — not available with raw OpenTelemetry
- • Built-in security scanning — automatic variable redaction on snapshots
- • Auto-instrumentation included — Express, database, and HTTP calls traced automatically
⚡ Performance Overhead
TraceKit is built on OpenTelemetry's efficient batch processing pipeline. The SDK adds minimal overhead to your Node.js application.
Request Tracing
< 1ms per request
Spans are batched and exported asynchronously.
Code Monitoring (Idle)
Zero overhead
No performance impact when no active breakpoints.
Code Monitoring (Capture)
< 5ms per snapshot
Includes variable serialization and security scanning.
Memory Footprint
~10-20 MB
SDK runtime and span buffer.
SDK Initialization
< 200ms one-time
One-time cost at application startup. Does not affect request latency.
Note: Performance characteristics are typical for production workloads and may vary with application complexity, request volume, and number of instrumented libraries. Use sampling configuration to reduce overhead in high-traffic services.
✅ Best Practices
✓ DO: Initialize TraceKit BEFORE requiring routes/modules
The require('tracekit').init() call must be the first line in your entry file so auto-instrumentation patches http, express, and database drivers before they are loaded.
// app.js - TraceKit MUST be first
require('tracekit').init();
const express = require('express');
const app = express();
✓ DO: Use environment variables for API keys
Store your API key in TRACEKIT_API_KEY rather than hardcoding it. The SDK reads this automatically.
✓ DO: Use process.on('SIGTERM') for graceful shutdown
Register a shutdown handler to flush pending spans before the process exits. This is critical in containerized environments where SIGTERM precedes termination.
process.on('SIGTERM', async () => {
await tracekit.shutdown();
process.exit(0);
});
✓ DO: Enable code monitoring in staging first
Test breakpoint capture and snapshot behavior in a staging environment before rolling out to production.
✓ DO: Use sampling in high-traffic services
Set TRACEKIT_SAMPLING_RATE to a value below 1.0 for services handling thousands of requests per second to reduce overhead without losing visibility.
✓ DO: Set meaningful service names
Use TRACEKIT_SERVICE_NAME to give your service a descriptive name that makes it easy to identify in the trace viewer.
✗ DON'T: Use dynamic import() for auto-instrumented modules
Auto-instrumentation patches require() calls. If you use ESM import() for modules like express or pg, they may not be instrumented. Use require() for these modules.
✗ DON'T: Create spans for every function
Trace boundaries like HTTP handlers, database calls, and external service calls. Instrumenting internal helper functions adds noise and overhead without useful insight.
✗ DON'T: Add high-cardinality attributes
Avoid using user IDs, request IDs, or session tokens as span attributes. These create excessive unique time series and degrade query performance.
✗ DON'T: Disable TLS in production
The TRACEKIT_INSECURE flag is for local development only. Always use TLS when sending traces to TraceKit in production.
Next Steps
- • Explore your traces on the Traces page to identify performance bottlenecks
- • Set up alert rules to get notified when issues occur
- • Add custom spans for specific operations you want to measure
- • Configure sampling rates for high-traffic applications
Pro Tip
Use the getClient() function to access the TraceKit client anywhere in your application for creating custom spans!