How to setup Prometheus Metrics for NestJS GraphQL
Tags: prometheus, nestjs, graphql, grafana
Published: 2022-11-16 10:19:43

TLDR: https://github.com/Shinigami92/nestjs-prometheus-graphql

---

Start of this week (14. Nov 2022) I needed to setup Prometheus + Grafana for an API-Gateway in my company.

I was afraid that nearly nothing already exists out there 🙀 and I only had this (spoiler: broken) guide https://hodovi.cc/blog/nestjs-apollo-graphql-prometheus-metrics-and-grafana-dashboards

The whole code examples on that blog article are not just copy&paste-able but also stale/broken.

So I needed to put some hard work into how to wire everything up and to safe hopefully some work for other devs like me, I created now a sample minimal reproducible repository and this dev.to article.

If this helped you at any time, feel free to sponsor me a cup of coffee ☕️ [PayPal](https://www.paypal.com/donate?hosted_button_id=L7GY729FBKTZY) or via https://github.com/sponsors/Shinigami92

---

I assume you already have a running GraphQL NestJS app, so I will skip these steps, otherwise please follow the instructions at https://docs.nestjs.com/graphql/quick-start

First we need to install some dependencies:
```bash
npm add @willsoto/nestjs-prometheus prom-client # The general Prometheus metrics provider which will expose the endpoint /metrics
npm add apollo-metrics apollo-tracing@0.15.0 # Provides Apollo GraphQL server plugins
npm add --save-dev apollo-server-plugin-base # Contains the ApolloServerPlugin interface
```

Now we will add a module file `src/metrics/prom.module.ts`

```ts
import { Module } from "@nestjs/common";
import { PrometheusModule } from "@willsoto/nestjs-prometheus";
import createMetricsPlugin from "apollo-metrics";
import { plugin as apolloTracingPlugin } from "apollo-tracing";
import { register } from "prom-client";

export const TRACING_PLUGIN_KEY = "TRACING_PLUGIN_KEY";
export const METRICS_PLUGIN_KEY = "METRICS_PLUGIN_KEY";

@Module({
  imports: [
    // Register the general Prometheus module
    PrometheusModule.register()
  ],
  providers: [
    {
      provide: TRACING_PLUGIN_KEY,
      // Provide the apollo tracing plugin
      // This is only needed if you also want to measure timings
      useValue: apolloTracingPlugin(),
    },
    {
      provide: METRICS_PLUGIN_KEY,
      // Provide the apollo metrics plugin
      useValue: createMetricsPlugin(register),
    },
  ],
  exports: [TRACING_PLUGIN_KEY, METRICS_PLUGIN_KEY],
})
export class PromModule {}
```

The tracing plugin is only needed if you also want to measure timings of your field resolvers. At this time, there is NO Grafana Dashboard that will display this data. Maybe I will create one in the future, but for now at least just lets collect these additional data.
The tracing plugin will be used here [apollo-metrics index L124](https://github.com/dotellie/apollo-metrics/blob/714770eaa8e71cfdc7caebb1412a02a1282f4ee7/src/index.ts#L124)

Now we need to register the `PromModule` in our `AppModule`

```ts
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
import { Module } from "@nestjs/common";
import { GraphQLModule } from "@nestjs/graphql";
import { ApolloServerPlugin } from "apollo-server-plugin-base";
import { join } from "path";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { CatsModule } from "./cats/cats.module";
import {
  METRICS_PLUGIN_KEY,
  PromModule,
  TRACING_PLUGIN_KEY,
} from "./metrics/prom.module";

@Module({
  imports: [
    // Import the PromModule generally (e.g. also for other metrics like non GraphQL stuff)
    PromModule,
    CatsModule,
    GraphQLModule.forRootAsync<ApolloDriverConfig>({
      driver: ApolloDriver,
      // Import the PromModule for GraphQL
      imports: [PromModule],
      useFactory: (
        // Use provided plugins injected from below
        tracingPlugin: ApolloServerPlugin,
        metricsPlugin: ApolloServerPlugin
      ) => ({
        debug: true,
        introspection: true,
        playground: true,
        // Plugins added to apollo server
        plugins: [tracingPlugin, metricsPlugin],
        autoSchemaFile: join(process.cwd(), "src/schema.gql"),
      }),
      // We need to inject the provider keys
      inject: [TRACING_PLUGIN_KEY, METRICS_PLUGIN_KEY],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```

> Warning: It is important to provide the plugins via that way, as otherwise if not registered to the NestJS context e.g. E2E tests will fail because the prom-client can only register a metric name once and will fail if a plugin tries to recreate a metric.

---

Now we are ready and just need to connect our metrics endpoint to Prometheus and then add Prometheus as Datasource for Grafana. (Please watch some other tutorials for that)

Now import the Grafana Dashboard: https://grafana.com/grafana/dashboards/16188-graphql-metrics

> ⚠️ But attention! ⚠️ You need to reconfigure the parameter `operation_name` to `operationName` (snake_case to camelCase)

![renamed operationName](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3o8stslkxg81051idxn.png)

The blog article https://hodovi.cc/blog/nestjs-apollo-graphql-prometheus-metrics-and-grafana-dashboards was manually using its own metric names 🤷

---

Please let me know if this was helpful and leave some comments.

---

## Edit

I started to edit the dashboard provided by Adin Hodovic and extended it with tracing/timing panels.

Grafana Dashboard (json export): https://github.com/Shinigami92/nestjs-prometheus-graphql/blob/main/ApolloGraphQLMetrics.grafana-dashboard.json

I tried to publish it at grafana.com dashboards, but somehow the upload does not work 🤷