Skip to content

GraphQL Monitoring — Metrics, Alerts, and Performance Dashboards for APIs

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about GraphQL Monitoring. We cover key concepts, practical examples, and best practices to help you master this topic.

GraphQL monitoring tracks key performance indicators — query latency, error rates, throughput, cache hit ratios, and resolver costs — to ensure your API remains fast, reliable, and scalable under load.

What You'll Learn

  • Key metrics to monitor for GraphQL APIs
  • Setting up Prometheus metrics for Apollo Server
  • Apollo Studio reporting and analytics
  • Building Grafana dashboards
  • Alerting on slow queries and error spikes
  • Monitoring subscription connections

Why It Matters

A GraphQL API can degrade gradually — a new field adds a slow JOIN, a client starts requesting larger pages, or a resolver develops a memory leak. Monitoring catches these regressions before they impact users. DodaTech's Durga Antivirus Pro monitors 50+ GraphQL metrics across 20 server instances, alerting the on-call team if p99 latency exceeds 2 seconds or error rate exceeds 1%.

Real-World Use

The monitoring dashboard shows a sudden spike in p95 latency for the scanResults field. The team traces it to a recent deployment that added a GROUP BY query without an index. They roll back the change and add the missing index, restoring latency within 10 minutes.

flowchart TB
    A["Apollo Server"] --> B["Metrics Exporter"]
    B --> C["Prometheus\n(time-series DB)"]
    C --> D["Grafana Dashboard"]
    C --> E["Alertmanager"]
    E --> F["PagerDuty"]
    E --> G["Slack"]
    B --> H["Apollo Studio\n(managed reporting)"]
    style F fill:#fecaca,stroke:#dc2626
    style G fill:#fef3c7,stroke:#d97706

Code Examples

Example 1: Prometheus Metrics Plugin

const promClient = require('prom-client');
const { ApolloServerPlugin } = require('apollo-server-plugin-base');

const queryDuration = new promClient.Histogram({
  name: 'graphql_query_duration_seconds',
  help: 'GraphQL query duration in seconds',
  labelNames: ['operation', 'type'],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10],
});

const queryErrors = new promClient.Counter({
  name: 'graphql_query_errors_total',
  help: 'Total GraphQL query errors',
  labelNames: ['operation', 'error_code'],
});

const activeSubscriptions = new promClient.Gauge({
  name: 'graphql_active_subscriptions',
  help: 'Number of active subscriptions',
});

const monitoringPlugin = {
  async requestDidStart({ request }) {
    const end = queryDuration.startTimer({
      operation: request.operationName || 'unknown',
    });
    
    return {
      async willSendResponse({ errors }) {
        end({ type: errors ? 'error' : 'success' });
        
        if (errors) {
          errors.forEach(err => {
            queryErrors.inc({
              operation: request.operationName || 'unknown',
              error_code: err.extensions?.code || 'UNKNOWN',
            });
          });
        }
      },
    };
  },
};

Example 2: Resolver-Level Performance Tracking

const resolverDuration = new promClient.Histogram({
  name: 'graphql_resolver_duration_seconds',
  help: 'Per-resolver duration in seconds',
  labelNames: ['type', 'field'],
  buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
});

function monitoredResolver(resolver, parentType, fieldName) {
  return async (parent, args, context, info) => {
    const end = resolverDuration.startTimer({
      type: parentType,
      field: fieldName,
    });
    
    try {
      const result = await resolver(parent, args, context, info);
      end({ result: 'success' });
      return result;
    } catch (error) {
      end({ result: 'error' });
      throw error;
    }
  };
}

// Track data loader performance
const loaderHits = new promClient.Counter({
  name: 'graphql_dataloader_hits_total',
  help: 'DataLoader cache hits',
});

const loaderMisses = new promClient.Counter({
  name: 'graphql_dataloader_misses_total',
  help: 'DataLoader cache misses',
});

Example 3: Health Check and Uptime Monitoring

const { ApolloServer } = require('apollo-server');
const express = require('express');
const promClient = require('prom-client');

const app = express();

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    uptime: process.uptime(),
    memoryUsage: process.memoryUsage(),
    activeRequests: activeRequests.value(),
  });
});

// Metrics endpoint for Prometheus
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', promClient.register.contentType);
  res.end(await promClient.register.metrics());
});

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [monitoringPlugin, loggingPlugin],
});

app.listen(4000, () => {
  console.log('Server running on port 4000');
  console.log('Metrics at http://localhost:4000/metrics');
  console.log('Health at http://localhost:4000/health');
});

Common Mistakes

  1. Monitoring only HTTP-level metrics — HTTP status codes (all 200) don't distinguish a fast query from a slow one. Monitor GraphQL-level metrics like resolver durations.
  2. Not setting up alert thresholds — collecting metrics is useless without alerts. Set PagerDuty alerts for p99 latency > 3s and error rate > 2%.
  3. Ignoring subscription metrics — monitor active subscription count, message rate, and connection duration. A spike in subscriptions may indicate a client bug.
  4. Not tracking per-field performance — an overall p50 of 100ms can hide a field that takes 5 seconds. Track resolver-level durations.
  5. Forgetting cache metrics — monitor cache hit ratio. A sudden drop indicates a misconfiguration or invalidation bug.

Practice Questions

  1. What are the five most important metrics for a GraphQL API?
  2. How do you distinguish between resolver-level and query-level monitoring?
  3. What should trigger a PagerDuty alert for a GraphQL API?
  4. How do you monitor subscription health?
  5. What is the difference between p50, p95, and p99 latency?

Challenge: Design a monitoring dashboard with Grafana for a GraphQL API that shows: request rate by operation, p50/p95/p99 latency, error rate by code, resolver duration heatmap, cache hit ratio, active subscriptions, and database query count. Include alert thresholds for each.

Mini Project

Build a complete monitoring stack for a GraphQL API: Prometheus metrics exporter plugin, Grafana dashboard with 6+ panels, PagerDuty alerting on error rate and latency thresholds, and a health check endpoint with uptime tracking.

FAQ

What is the difference between Apollo Studio and Prometheus monitoring?

Apollo Studio focuses on per-operation traces and schema usage. Prometheus provides long-term metrics storage and alerting. Use both for comprehensive monitoring.

How do I monitor GraphQL subscriptions?

Track active subscription count (Gauge), messages per second (Counter), connection duration (Histogram), and disconnect reasons. Alert on sudden connection drops.

What latency thresholds should I set for alerts?

Warning at p95 > 1s, critical at p99 > 3s. For resolvers, warning at > 500ms, critical at > 2s. Adjust based on your API's baseline.

How do I monitor DataLoader performance?

Track cache hit vs miss counts. A hit ratio below 80% indicates the DataLoader isn't effectively batching queries. Check for N+1 problems.

Should I monitor each field individually?

Monitor fields that make database calls or external API requests. Scalar fields that just return a cached value don't need individual monitoring.

What's Next

Learn about securing your GraphQL API

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro