Use this guide to instrument a Node.js GraphQL server with OpenTelemetry and send traces to SigNoz. The GraphQL instrumentation captures graphql.parse, graphql.validate, per-operation spans named after the operation type (for example, query), and per-resolver graphql.resolve spans without application code changes.
Works with Apollo Server, graphql-http, and any server built on graphql-js.
Prerequisites
- Node.js 20.6.0+
- If you're on Node 18, use 18.19.0+. Node 18 reached end of life on March 27, 2025.
- A GraphQL server using graphql-js v14+
- An instance of SigNoz (either Cloud or Self-Hosted)
Send traces to SigNoz
Step 1. Set environment variables
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: Name of your service (e.g.,graphql-api)
Step 2. Install OpenTelemetry packages
npm install --save @opentelemetry/api @opentelemetry/auto-instrumentations-node
@opentelemetry/auto-instrumentations-node includes and enables @opentelemetry/instrumentation-graphql.
Step 3. Run your server
node server.js
Step 1. Set environment variables
Add OpenTelemetry environment variables to your Kubernetes deployment manifest:
env:
- name: OTEL_TRACES_EXPORTER
value: 'otlp'
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: 'https://ingest.<region>.signoz.cloud:443'
- name: OTEL_NODE_RESOURCE_DETECTORS
value: 'env,host,os'
- name: OTEL_SERVICE_NAME
value: '<service-name>'
- name: OTEL_EXPORTER_OTLP_HEADERS
value: 'signoz-ingestion-key=<your-ingestion-key>'
- name: NODE_OPTIONS
value: '--require @opentelemetry/auto-instrumentations-node/register'
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: Name of your service (e.g.,graphql-api)
Step 2. Install OpenTelemetry packages
Ensure your application image includes the OpenTelemetry packages:
npm install --save @opentelemetry/api @opentelemetry/auto-instrumentations-node
Step 3. Deploy and run
Apply your deployment to the cluster. Node.js loads OpenTelemetry when your application starts.
The OpenTelemetry Operator injects instrumentation into your Node.js pods without modifying your application image.
Step 1. Set up the OpenTelemetry Operator
Install the Operator and Collector following the K8s OTel Operator installation guide.
Step 2. Create the Instrumentation resource
Create instrumentation.yaml to configure Node.js auto-instrumentation:
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: graphql-instrumentation
spec:
exporter:
endpoint: http://otel-collector-collector:4318
propagators:
- tracecontext
- baggage
nodejs:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
Deploy this resource to your cluster.
Step 3. Add annotations to your deployment
Add these annotations to your pod template's metadata.annotations:
instrumentation.opentelemetry.io/inject-nodejs: 'true'
Step 1. Add OpenTelemetry to your Dockerfile
# ... your existing build steps ...
# Install OpenTelemetry packages
RUN npm install @opentelemetry/api @opentelemetry/auto-instrumentations-node
# Set OpenTelemetry environment variables
ENV OTEL_TRACES_EXPORTER="otlp"
ENV OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
ENV OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
ENV OTEL_SERVICE_NAME="<service-name>"
ENV OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
ENV NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
# ... your CMD or ENTRYPOINT ...
Or pass them at runtime:
docker run \
-e OTEL_TRACES_EXPORTER="otlp" \
-e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443" \
-e OTEL_NODE_RESOURCE_DETECTORS="env,host,os" \
-e OTEL_SERVICE_NAME="<service-name>" \
-e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>" \
-e NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register" \
your-image:latest
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: Name of your service (e.g.,graphql-api)
Step 2. Build and run
docker build -t my-graphql-app . && docker run -d -p 4000:4000 my-graphql-app
Validate
Send a query to your GraphQL server, then open SigNoz:
- Go to Services and find your service. Click Refresh if it does not appear.
- Go to Traces and filter by your service name.
- Open a trace to see the operation span (for example,
query), andgraphql.resolvespans. Depending on your setup, you may also seegraphql.parseandgraphql.validatespans.


GraphQL-specific configuration
@opentelemetry/auto-instrumentations-node enables GraphQL instrumentation with default settings. To tune those settings, install the GraphQL instrumentation package and configure it in code.
Step 1. Install the package
npm install --save @opentelemetry/instrumentation-graphql
Step 2. Create a tracing file
'use strict'
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { GraphQLInstrumentation } = require('@opentelemetry/instrumentation-graphql');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { resourceFromAttributes } = require('@opentelemetry/resources');
const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'https://ingest.<region>.signoz.cloud:443/v1/traces',
headers: { 'signoz-ingestion-key': '<your-ingestion-key>' },
}),
instrumentations: [
getNodeAutoInstrumentations({
// Disable the built-in GraphQL instrumentation so the explicit one below takes effect
'@opentelemetry/instrumentation-graphql': { enabled: false },
}),
new GraphQLInstrumentation({
// Record resolver spans up to this depth (default: all)
depth: 2,
// Merge array resolver spans into one span instead of one per item
mergeItems: true,
// Skip resolvers that return a field value without custom logic
ignoreTrivialResolveSpans: true,
}),
],
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: '<service-name>',
}),
});
sdk.start();
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'https://ingest.<region>.signoz.cloud:443/v1/traces',
headers: { 'signoz-ingestion-key': '<your-ingestion-key>' },
}),
instrumentations: [
getNodeAutoInstrumentations({
// Disable the built-in GraphQL instrumentation so the explicit one below takes effect
'@opentelemetry/instrumentation-graphql': { enabled: false },
}),
new GraphQLInstrumentation({
// Record resolver spans up to this depth (default: all)
depth: 2,
// Merge array resolver spans into one span instead of one per item
mergeItems: true,
// Skip resolvers that return a field value without custom logic
ignoreTrivialResolveSpans: true,
}),
],
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: '<service-name>',
}),
});
sdk.start();
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: Name of your service (e.g.,graphql-api)
Step 3. Load the tracing file before your server
node -r ./tracing.js server.js
Compile tracing.ts to tracing.js with your TypeScript build, then preload the emitted JavaScript file:
node -r ./tracing.js server.js
Troubleshooting
No GraphQL spans appearing
- Confirm
graphql-jsis installed in your project. The instrumentation patches thegraphqlpackage; it does not work if GraphQL is bundled inside another package without a top-levelgraphqldependency. - Set
OTEL_LOG_LEVEL=debugand restart. Look for lines mentioning@opentelemetry/instrumentation-graphqlto confirm the instrumentation loaded.
Only HTTP spans appear, no resolver spans
ignoreTrivialResolveSpans defaults to false, so GraphQL instrumentation includes trivial resolvers. If you enabled it and resolver spans are missing, disable it to confirm the resolvers are being called.
Enable debug logging
export OTEL_LOG_LEVEL=debug
Look for query, graphql.parse, graphql.validate, or graphql.resolve in the span output.
Service not appearing in SigNoz
- Verify
OTEL_EXPORTER_OTLP_ENDPOINTandOTEL_EXPORTER_OTLP_HEADERSare set correctly. - Check network connectivity to the SigNoz endpoint.
- Make sure your server receives GraphQL requests. Spans appear after a query runs.
Setup OpenTelemetry Collector (Optional)
What is the OpenTelemetry Collector?
The OTel Collector sits between your application and SigNoz. Your application sends telemetry to the Collector, and the Collector forwards it to SigNoz.
Why use it?
- Clean up data: Filter noisy traces or remove sensitive fields before telemetry leaves your servers.
- Keep your app lightweight: Let the Collector handle batching, retries, and compression instead of your application code.
- Add resource context: Tag telemetry with details such as the Kubernetes pod or cloud region.
- Route telemetry to more backends: Send data to multiple destinations without changing your app.
See Switch from direct export to Collector for step-by-step instructions.
Next steps
- Manual instrumentation for adding custom spans and attributes to your resolvers
- Selective instrumentation to enable or disable specific libraries
- Send Node.js metrics alongside traces
- Send Node.js logs to correlate logs with traces