nestjs-prometheus: a metrics module for NestJS
NestJS module for Prometheus
At a glance
- What is it?
- nestjs-prometheus turns a NestJS application into something Prometheus can scrape. It registers a metrics endpoint, exposes counters, gauges, histograms, and summaries as injectable providers, lets a collect callback depend on your own services, and gives you a controller you can subclass when the default one is not enough.
- Who is it for?
- nestjs-prometheus fits a team running a NestJS service that needs to be scraped, and that wants the metric definitions to live in the dependency injection container rather than in a separate metrics file. It does not fit a service that already collects metrics through a different mechanism, and it does not fit a codebase that has not moved to the module and provider system, because the whole design is built on NestJS's own registration model.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One import, one endpoint
nestjs-prometheus is a NestJS module for Prometheus, and the smallest possible use of it is a module import.
The install is the module plus the Prometheus client library, with the client installed alongside rather than assumed.
yarn add @willsoto/nestjs-prometheus @prometheus-io/clientThe registration is a static call on the module class, and the application module then imports the result.
import { Module } from "@nestjs/common";
import { PrometheusModule } from "@willsoto/nestjs-prometheus";
@Module({
imports: [PrometheusModule.register()],
})
export class AppModule {}That is the whole integration. By default the module registers a metrics endpoint, at the path slash metrics, which returns the default metrics of the client library, the process and Node runtime numbers that any Prometheus setup expects to find.
The project is Apache 2.0 licensed, the last push was on 2026-10-19, and the release history shows a major version landed recently, with 7.0.0 in October 2026 after 6.1.1 in September and 6.1.0 in March.
The design choice worth naming is that the module exposes the default metrics rather than nothing. A service that exposes a metrics endpoint with nothing on it is a service that has to be configured twice, once in the application and once in the scrape configuration, and this one saves the second step by shipping the standard set.
Four options on the register call
The register function takes an options object, and the documentation walks through each of them with a short example.
The path option changes the metrics HTTP endpoint, and the example sets it to a custom path. That matters when the default would collide with a route in the application, or when the endpoint should sit behind a path prefix.
The defaultMetrics option has two parts. The enabled flag switches the collection of the default metrics off, and the config object is passed through to the client library's own configuration, with a link to that documentation. The two are separate because they answer different questions: whether the default metrics appear at all, and how they are configured when they do.
The defaultLabels option adds labels to every metric the module exposes, and the example sets an application name. The documentation points at the client library's own page on default labels, which are segmented by registry, and the reason to know that is that a default label is not a label you attach to one metric, it is a label on everything, which is how you keep a metric from one service and a metric from another apart in a shared Prometheus.
And the customMetricPrefix option prefixes all custom metrics, so the metrics your application defines do not collide with the default ones or with anything another library might register under a similar name.
Those four options are the entire configuration surface of the module, which is a small surface for a library that has been at version seven.
Metrics as providers
Custom metrics are declared as providers rather than created where you use them, and that is the part that makes this a NestJS library rather than a wrapper.
There is a provider factory for each metric type, and the factories take a name and a help string.
import { Module } from "@nestjs/common";
import { PrometheusModule, makeCounterProvider } from "@willsoto/nestjs-prometheus";
import { Service } from "./service";
@Module({
imports: [PrometheusModule.register()],
providers: [
Service,
makeCounterProvider({
name: "metric_name",
help: "metric_help",
}),
],
})
export class AppModule {}The counter is then injected wherever you need it, with a decorator that takes the metric name.
import { Injectable } from "@nestjs/common";
import { InjectMetric } from "@willsoto/nestjs-prometheus";
import { Counter } from "@prometheus-io/client";
@Injectable()
export class Service {
constructor(@InjectMetric("metric_name") public counter: Counter<string>) {}
}So a metric is a dependency. The service does not reach for a global registry, it asks the container for the metric it was told about, and the counter it receives is typed. The four types are the four the Prometheus client library offers, a counter, a gauge, a histogram, and a summary, and each has its own factory function with the same shape.
That design has a second effect that is easy to miss. Because the metric is a provider, it can be mocked in a test in the same way as any other dependency, which is a considerable advantage over code that reaches into a global registry.
Where the module is registered changes the behaviour
There is one subtlety in the documentation that is worth its own section, because it is the kind of thing that costs an afternoon.
The customMetricPrefix option adds a prefix to custom metrics, but the caveat is about registration rather than about naming. To have custom metrics registered in a different module from the one where the Prometheus module was registered, you have to do one of two things.
The first, which the documentation marks as recommended, is to add the Prometheus module to the exports of the module that registers it. The note suggests that it may be useful to create a common module that registers and exports the Prometheus module, and then to import that module into whichever module holds the custom metrics.
The second, which is marked as not recommended, is to mark the Prometheus module as global.
So the module has a scope, and the scope decides where metric providers can live. The recommended pattern is a shared module, and the global flag is the shortcut that saves a line of configuration at the cost of a module that is visible everywhere, which in a large application is the difference between a dependency you can find and one you cannot.
That is the whole of the guidance, and it is the part of the documentation to read before a large application is wired up, because changing it later means moving providers around.
Collect callbacks that can depend on your services
Every metric provider accepts an optional inject array, and that array is what makes the library more than a counter you increment by hand.
With it, you can inject NestJS dependencies into the collect callback, and the injected dependencies arrive as arguments to that function. The example uses a queue token to get the waiting count of a scheduled queue and set it as a gauge.
import { Module } from "@nestjs/common";
import { PrometheusModule, makeGaugeProvider } from "@willsoto/nestjs-prometheus";
@Module({
imports: [PrometheusModule.register()],
providers: [
makeGaugeProvider({
name: "queue_size",
help: "Number of waiting items in the queue",
inject: [getQueueToken("scheduledQueue")],
collect(queue) {
const value = await queue.getWaitingCount();
this.set(value);
},
}),
],
})
export class AppModule {}The point of that is that a gauge can be sampled from the state of the system rather than tracked by hand. Nobody increments a queue depth counter; the queue knows its own depth, and the collect callback asks it at scrape time. The same pattern covers a connection pool size, a cache hit rate, or a value that lives in a database, and it means the numbers cannot drift out of step with the thing they measure.
The documentation notes that this works with all four metric types, so a counter, a gauge, a histogram, and a summary can each have a collect callback with dependencies.
A controller you can subclass, and a modern toolchain
The last feature in the documentation is the escape hatch. If you need special logic, or you want access to the controller itself, for instance to customise the Swagger documentation, you can provide your own controller or subclass the default one.
import { Controller, Get, Res } from "@nestjs/common";
import { PrometheusController } from "@willsoto/nestjs-prometheus";
import { Response } from "express";
@Controller()
class MyCustomController extends PrometheusController {
@Get()
async index(@Res({ passthrough: true }) response: Response) {
return super.index(response);
}
}The controller is a normal NestJS controller, so the response is passed through with the decorator flag, and the default behaviour is reached by calling the superclass method. That is the smallest possible example, and the documentation says it is enough to extend or customise in whatever way you need.
The toolchain is worth a note, because it says something about the project's age and its direction. The package builds with the TypeScript compiler against a build configuration and type checks against a separate lint configuration with no emit. The formatter is oxfmt and the linter is oxlint, which is a move away from the Prettier and ESLint pairing most projects have, and the formatting is run through a lefthook hook installed by a prepare script, so the same checks run on commit and in continuous integration. The tests run under vitest, with a coverage variant and a watch mode, and the release is driven by semantic-release with conventional commits enforced by commitlint.
The peer dependencies are the part to check before upgrading. The NestJS common package is accepted from version 7 through version 12, and the Prometheus client from 0.16, which is a wide range, and the development dependencies sit on the current NestJS 12 line. So the module declares compatibility with seven major versions of the framework, and is built and tested against the newest one.
Editorial conclusion
nestjs-prometheus fits a team running a NestJS service that needs to be scraped, and that wants the metric definitions to live in the dependency injection container rather than in a separate metrics file. It does not fit a service that already collects metrics through a different mechanism, and it does not fit a codebase that has not moved to the module and provider system, because the whole design is built on NestJS's own registration model. Verify first that your NestJS major version is inside the peer range the package declares, which spans several majors, that you install the Prometheus client library alongside it rather than relying on it to be pulled in, and that you register the module where your metric providers live, since the documentation treats that as the one decision that changes how the rest behaves.
Frequently asked questions
How do I add a metrics endpoint to a NestJS application?
Install the module and the Prometheus client library, then import the result of the register call in your application module. By default the module exposes a metrics endpoint at the slash metrics path, returning the default metrics of the client library, and the path option changes where it lives.
How do I define a custom metric in nestjs-prometheus?
Add a provider factory to your module, for example the counter provider with a name and a help string, then inject it into your service with the metric decorator and the metric name. Because the metric is a provider rather than a global, it can also be mocked in tests.
What is the inject option for in a metric provider?
It lets you inject NestJS dependencies into the collect callback, and the injected dependencies arrive as arguments to that function. The example injects a queue token and sets the waiting count as a gauge value, which means a gauge can be sampled from live system state rather than tracked by hand.
Why does the nestjs-prometheus documentation warn about where the module is registered?
Because custom metrics must be registered in a module that can see the Prometheus module, either by adding it to the exports of the registering module, which the documentation recommends, or by marking it as global, which it does not recommend. The recommended pattern is a shared module that registers and exports it.
Which NestJS versions does nestjs-prometheus support?
The peer dependency on the NestJS common package accepts versions 7 through 12, and the Prometheus client is accepted from 0.16. The development dependencies sit on the NestJS 12 line, and the tests run under vitest.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/willsoto-nestjs-prometheus)Was this useful?