Skip to main content

GraphQL Watcher

The GraphQL Watcher tracks all GraphQL operations (queries, mutations, and subscriptions) in your NestJS application, capturing detailed information about performance, errors, and N+1 query detection.

What Gets Captured

  • Operation type (query, mutation, subscription)
  • Operation name and query string
  • Variables (with sensitive value masking)
  • Response data (optional)
  • GraphQL errors
  • Execution timing (parsing, validation, execution)
  • Field-level resolver traces (optional)
  • N+1 query detection
  • Subscription lifecycle events
  • Client IP and user agent
  • Authenticated user information
  • Batch operation tracking

Supported GraphQL Servers

NestLens automatically detects and supports:

  • Apollo Server (@apollo/server)
  • Mercurius (Fastify GraphQL)

Configuration

NestLensModule.forRoot({
watchers: {
graphql: {
enabled: true,
server: 'auto', // 'apollo' | 'mercurius' | 'auto'
captureVariables: true,
captureResponse: false,
ignoreIntrospection: true,
ignoreOperations: ['HealthCheck'],
detectN1Queries: true,
n1Threshold: 10,
traceFieldResolvers: false,
subscriptions: {
enabled: true,
trackMessages: false,
trackConnectionEvents: true,
},
tags: async (ctx) => {
return [ctx.operationType, ctx.operationName ?? 'anonymous'];
},
},
},
})

Configuration Options

OptionTypeDefaultDescription
enabledbooleanfalseEnable/disable GraphQL tracking
serverstring'auto'GraphQL server type: 'apollo', 'mercurius', or 'auto'
maxQuerySizenumber8192Maximum query size to capture (bytes)
captureVariablesbooleantrueCapture operation variables
sensitiveVariablesstring[]['password', 'token', ...]Variable names to mask
ignoreIntrospectionbooleantrueIgnore introspection queries
ignoreOperationsstring[][]Operation names to ignore
traceFieldResolversbooleanfalseEnable field-level resolver tracing
traceSlowResolversnumberundefinedOnly trace resolvers slower than this (ms)
resolverTracingSampleRatenumber0.1Sample rate for resolver tracing (0-1)
detectN1QueriesbooleantrueEnable N+1 query detection
n1Thresholdnumber10Resolver call count to trigger N+1 warning
samplingRatenumber1.0Overall sampling rate (0-1)
captureResponsebooleanfalseCapture response data
maxResponseSizenumber65536Maximum response size to capture (bytes)
tagsfunctionundefinedFunction to generate custom tags

Subscription Options

OptionTypeDefaultDescription
enabledbooleantrueTrack subscriptions
trackMessagesbooleanfalseTrack individual subscription messages
captureMessageDatabooleanfalseCapture message payload data
maxTrackedMessagesnumber100Maximum messages to track per subscription
trackConnectionEventsbooleantrueTrack connection/disconnection events
transportModestring'auto'Transport mode: 'gateway', 'adapter', 'auto'

Payload Structure

interface GraphQLEntry {
type: 'graphql';
payload: {
operationName?: string; // Operation name (if named)
operationType: 'query' | 'mutation' | 'subscription';
query: string; // GraphQL query string
queryHash: string; // Hash for grouping similar queries
variables?: Record<string, unknown>;

// Timing
duration: number; // Total duration (ms)
parsingDuration?: number; // Query parsing time (ms)
validationDuration?: number; // Query validation time (ms)
executionDuration?: number; // Resolver execution time (ms)

// Response
statusCode: number; // HTTP status (200, 400, 500, etc.)
hasErrors: boolean; // Whether response contains errors
errors?: GraphQLErrorInfo[]; // GraphQL errors
responseData?: unknown; // Response data (if captured)

// Performance
resolverCount?: number; // Number of resolver calls
fieldCount?: number; // Number of fields in selection
depthReached?: number; // Maximum query depth

// N+1 Detection
potentialN1?: PotentialN1Warning[];

// Client context
ip?: string;
userAgent?: string;
user?: RequestUser;

// Batching
batchIndex?: number; // Index in batch (0-based)
batchSize?: number; // Total batch size
batchId?: string; // Batch identifier

// Subscriptions
subscriptionId?: string;
subscriptionEvent?: 'start' | 'data' | 'error' | 'complete';
messageCount?: number;
subscriptionDuration?: number;

// Field traces (opt-in)
fieldTraces?: GraphQLFieldTrace[];
};
}

Usage Example

Apollo Server Integration

import { GraphQLWatcher } from 'nestlens';

@Module({
imports: [
NestLensModule.forRoot({
watchers: {
graphql: true,
},
}),
GraphQLModule.forRootAsync({
imports: [NestLensModule],
inject: [GraphQLWatcher],
useFactory: (graphqlWatcher: GraphQLWatcher) => ({
autoSchemaFile: true,
plugins: [graphqlWatcher.getPlugin()],
}),
}),
],
})
export class AppModule {}

Mercurius Integration

import { GraphQLWatcher } from 'nestlens';

// With Fastify adapter
fastify.register(mercurius, {
schema,
hooks: graphqlWatcher.getPlugin(),
});

N+1 Query Detection

The GraphQL Watcher automatically detects potential N+1 query issues by tracking resolver call patterns:

// Example: N+1 warning
{
potentialN1: [{
field: 'posts',
parentType: 'User',
count: 50,
suggestion: 'Consider using DataLoader for User.posts field'
}]
}

Configure N+1 detection:

NestLensModule.forRoot({
watchers: {
graphql: {
detectN1Queries: true,
n1Threshold: 10, // Warn when a resolver is called 10+ times
},
},
})

Field-Level Tracing

Enable detailed resolver timing for performance optimization:

NestLensModule.forRoot({
watchers: {
graphql: {
traceFieldResolvers: true,
resolverTracingSampleRate: 0.1, // 10% sampling
traceSlowResolvers: 50, // Only trace resolvers > 50ms
},
},
})

Subscription Tracking

Track WebSocket subscription lifecycle:

NestLensModule.forRoot({
watchers: {
graphql: {
subscriptions: {
enabled: true,
trackMessages: true, // Track each message
captureMessageData: false, // Don't capture message content
trackConnectionEvents: true,
},
},
},
})

Dashboard View

GraphQL Detail View

In the NestLens dashboard, GraphQL entries appear in the GraphQL tab showing:

  • Timeline view of all operations
  • Operation type badges (Query, Mutation, Subscription)
  • Error highlighting
  • N+1 warnings
  • Query hash grouping
  • Subscription lifecycle events
  • Field-level timing waterfall (when enabled)

Filters Available

  • Filter by operation type
  • Filter by operation name
  • Filter by error status
  • Filter by N+1 warnings
  • Filter by query hash
  • Filter by IP address
  • Search by query content

Sensitive Data Handling

The GraphQL Watcher automatically masks sensitive variables:

  • password, token, secret
  • apiKey, api_key
  • accessToken, access_token
  • refreshToken, refresh_token
  • authorization
  • creditCard, credit_card
  • ssn, pin

Customize sensitive variable detection:

NestLensModule.forRoot({
watchers: {
graphql: {
sensitiveVariables: [
'password',
'secret',
'myCustomSecret',
],
},
},
})

Performance Considerations

  • Introspection: Ignored by default to reduce noise
  • Sampling: Use samplingRate for high-traffic APIs
  • Field tracing: Enable only when debugging performance issues
  • Response capture: Disabled by default to reduce storage