A lightweight, zero-dependency TypeScript library for creating and exposing Prometheus metrics in standard exposition and OpenMetrics format.
npm install @sourceregistry/node-prometheus> A lightweight, zero-dependency TypeScript library for creating and exposing Prometheus & OpenMetrics-compatible metrics.


Supports:
- β
Counter
- β
Gauge
- β
Histogram
- β
Summary
- β
Untyped
Exports metrics in Prometheus exposition format and OpenMetrics v1.0.0 β ready for modern observability stacks.
---
``bash`
npm install @sourceregistry/node-prometheus
> β Zero external dependencies β pure TypeScript.
---
`ts
import { Counter } from '@sourceregistry/node-prometheus';
let requestCount = 0;
const counter = new Counter({
name: 'http_requests_total',
description: 'Total number of HTTP requests',
reader: () => [requestCount]
});
// Later...
requestCount++;
`
`ts
import { Gauge } from '@sourceregistry/node-prometheus';
const gauge = new Gauge({
name: 'current_temperature_celsius',
description: 'Current temperature in Celsius',
reader: () => [getTemperature()] // e.g., returns 23.5
});
`
`ts
import { Histogram } from '@sourceregistry/node-prometheus';
const histogram = new Histogram({
name: 'response_time_seconds',
description: 'HTTP response time in seconds',
buckets: [0.1, 0.5, 1, 2.5, 5]
});
histogram.observe(0.73); // Updates buckets, sum, and count
`
`ts
import { Summary } from '@sourceregistry/node-prometheus';
const summary = new Summary({
name: 'request_duration_seconds',
description: 'Request duration with quantiles',
quantiles: [0.5, 0.9, 0.99],
calculate: (value, quantile) => {
// Example: simple moving average
const current = / your state / 0;
return current 0.9 + value 0.1;
}
});
summary.observe(1.2);
`
`ts
import { Untyped } from '@sourceregistry/node-prometheus';
const untyped = new Untyped({
name: 'some_legacy_metric',
value: 42
});
untyped.set(43);
`
Use Metric.concat() to serialize multiple metrics into a single string. You can specify 'prometheus' (default) or 'openmetrics' format.
`ts
import { Metric, Counter, Gauge } from '@sourceregistry/node-prometheus';
let requestCount = 0;
const counter = new Counter({
name: 'http_requests_total',
description: 'Total HTTP requests',
reader: () => [requestCount]
});
const gauge = new Gauge({
name: 'cpu_usage_percent',
description: 'Current CPU usage',
reader: () => [Math.random() * 100]
});
// Serialize in Prometheus format (default)
const promOutput = await Metric.concat('prometheus', counter, gauge);
console.log(promOutput);
// # HELP http_requests_total ...
// # TYPE http_requests_total counter
// http_requests_total 0 1712345678901
// ...
// Serialize in OpenMetrics format
const omOutput = await Metric.concat('openmetrics', counter, gauge);
console.log(omOutput);
// # HELP http_requests_total ...
// # TYPE http_requests_total counter
// http_requests_total 0 1712345678901
// ...
// # EOF
`
> β
In OpenMetrics mode, # EOF is automatically appended, and trailing whitespace is trimmed.
---
This example uses Metric.concat(format, ...) to serve the correct format based on the clientβs Accept header.
`ts
// examples/server.ts
import { createServer } from 'http';
import { Counter, Gauge, Histogram, Metric, Untyped } from '@sourceregistry/node-prometheus';
const gauge = new Gauge({
name: 'random_gauge',
description: 'Random gauge value updated on each scrape',
reader: () => [Math.random() * 100],
});
const histogram = new Histogram({
name: 'random_histogram',
description: 'Histogram of random values',
buckets: [0.1, 0.2, 0.5, 1.0],
});
let hits = 0;
const counter = new Counter({
name: 'http_requests_total',
description: 'Total HTTP requests handled',
reader: () => [[hits, { method: 'GET', endpoint: '/metrics' }]],
});
const untyped = new Untyped({
name: 'uptime_seconds',
description: 'Server uptime in seconds',
value: [0, Date.now()],
});
// Simulate background metric updates
setInterval(() => {
histogram.observe(Math.random());
const uptime = (Date.now() - untyped.get()[1]) / 1000;
untyped.set(uptime);
}, 2000);
// HTTP Server with OpenMetrics negotiation
createServer(async (req, res) => {
console.log(Scraped at ${new Date().toISOString()});
hits++;
const acceptHeader = req.headers['accept'] || '';
const format = acceptHeader.includes('application/openmetrics-text') ? 'openmetrics' : 'prometheus';
res.writeHead(200, {
'Content-Type': format === 'openmetrics'
? 'application/openmetrics-text; version=1.0.0; charset=utf-8'
: 'text/plain; version=0.0.4; charset=utf-8',
});
const output = await Metric.concat(format, gauge, histogram, counter, untyped);
res.end(output);
}).listen(8080, '0.0.0.0', () => {
console.log('β
Metrics server: http://localhost:8080');
});
`
Run it:
`bash`
npm run example::http.server
Then test:
`bash`
curl -H "Accept: application/openmetrics-text" http://localhost:8080
curl http://localhost:8080 # defaults to Prometheus format
---
Compatible with classic Prometheus scrapers:
`HELP http_requests_total Total number of HTTP requests
TYPE http_requests_total counter
http_requests_total{method="GET",endpoint="/metrics"} 5 1712345678901
---
π OpenMetrics Format (Modern Standard)
When client sends
Accept: application/openmetrics-text, server responds with:`
HELP random_gauge Random gauge value updated on each scrape
TYPE random_gauge gauge
random_gauge 42.17 1712345678901HELP random_histogram Histogram of random values
TYPE random_histogram histogram
random_histogram_bucket{le="0.1"} 3 1712345678901
random_histogram_bucket{le="0.2"} 7 1712345678901
random_histogram_bucket{le="0.5"} 15 1712345678901
random_histogram_bucket{le="1.0"} 20 1712345678901
random_histogram_bucket{le="+Inf"} 20 1712345678901
random_histogram_sum 12.34 1712345678901
random_histogram_count 20 1712345678901HELP http_requests_total Total HTTP requests handled
TYPE http_requests_total counter
http_requests_total{method="GET",endpoint="/metrics"} 5 1712345678901HELP uptime_seconds Server uptime in seconds
TYPE uptime_seconds untyped
uptime_seconds 124.3 1712345678901EOF
`> β
Required:
+Inf bucket, # EOF, correct Content-Type.---
π οΈ Features
- β
Type-safe β Full TypeScript support with JSDoc.
- β
Async/Sync Readers β Gauge/Counter support both.
- β
Label Support β Per-metric and per-sample labels.
- β
Timestamps β Optional sample timestamps.
- β
Validation β Input sanitization and error handling.
- β
Extensible β Easy to extend or override behavior.
- β
OpenMetrics Ready β Auto-negotiation support in HTTP example.
- β
Zero Dependencies β Pure TypeScript, no bloat.
---
π§ͺ Testing
Comes with Vitest-compatible tests covering all metric types, serialization, edge cases, and error handling.
Run tests:
`bash
npm test # Watch mode
npm run test:ui # GUI
npm run test:coverage # Coverage report
`---
πΎ Examples
examples/ folder in this repository for:- Basic metric usage
- HTTP server with Prometheus/OpenMetrics support
- Histogram/Summary simulations
---
π License
Apache License 2.0 β See LICENSE for details.
---
π€ Contributing
PRs welcome! Please ensure:
- β
Code matches existing style and JSDoc standards
- β
Tests are added for new features
- β
No external dependencies added
- β
npm run build and npm test pass---
> π‘ Note: This library generates exposition format only. You must expose it via HTTP (e.g., Express, Fastify, or plain
http`) for Prometheus to scrape. See the HTTP example above to get started quickly.