{"id":13563235,"url":"https://github.com/jochen-schweizer/express-prom-bundle","last_synced_at":"2026-02-24T19:03:26.216Z","repository":{"id":9195086,"uuid":"56466363","full_name":"jochen-schweizer/express-prom-bundle","owner":"jochen-schweizer","description":"express middleware with standard prometheus metrics in one bundle","archived":false,"fork":false,"pushed_at":"2025-08-06T20:40:30.000Z","size":700,"stargazers_count":321,"open_issues_count":16,"forks_count":70,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-11-12T00:21:04.978Z","etag":null,"topics":["express-middleware","metrics","npm-package"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jochen-schweizer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-04-18T00:52:02.000Z","updated_at":"2025-09-26T15:37:02.000Z","dependencies_parsed_at":"2024-03-17T00:54:07.155Z","dependency_job_id":"02328428-7274-4a9b-ab4e-5b3bf8d58957","html_url":"https://github.com/jochen-schweizer/express-prom-bundle","commit_stats":{"total_commits":208,"total_committers":40,"mean_commits":5.2,"dds":0.4182692307692307,"last_synced_commit":"f9a0a7622a398da828c865b2c8a79b42150f6815"},"previous_names":[],"tags_count":45,"template":false,"template_full_name":null,"purl":"pkg:github/jochen-schweizer/express-prom-bundle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jochen-schweizer%2Fexpress-prom-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jochen-schweizer%2Fexpress-prom-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jochen-schweizer%2Fexpress-prom-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jochen-schweizer%2Fexpress-prom-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jochen-schweizer","download_url":"https://codeload.github.com/jochen-schweizer/express-prom-bundle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jochen-schweizer%2Fexpress-prom-bundle/sbom","scorecard":{"id":526148,"data":{"date":"2025-08-11","repo":{"name":"github.com/jochen-schweizer/express-prom-bundle","commit":"f9a0a7622a398da828c865b2c8a79b42150f6815"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.3,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":2,"reason":"Found 3/13 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 20 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":2,"reason":"8 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-593f-38f6-jp5m","Warn: Project is vulnerable to: GHSA-x2rg-q646-7m2v","Warn: Project is vulnerable to: GHSA-jgmv-j7ww-jx2x","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T04:26:36.574Z","repository_id":9195086,"created_at":"2025-08-20T04:26:36.574Z","updated_at":"2025-08-20T04:26:36.574Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29796796,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T16:37:37.581Z","status":"ssl_error","status_checked_at":"2026-02-24T16:37:37.074Z","response_time":75,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["express-middleware","metrics","npm-package"],"created_at":"2024-08-01T13:01:16.698Z","updated_at":"2026-02-24T19:03:26.187Z","avatar_url":"https://github.com/jochen-schweizer.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"[![build status](https://travis-ci.org/jochen-schweizer/express-prom-bundle.png)](https://travis-ci.org/jochen-schweizer/express-prom-bundle) [![Coverage Status](https://coveralls.io/repos/github/jochen-schweizer/express-prom-bundle/badge.svg?branch=master)](https://coveralls.io/github/jochen-schweizer/express-prom-bundle?branch=master) [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://www.tldrlegal.com/l/mit) [![NPM version](https://badge.fury.io/js/express-prom-bundle.png)](http://badge.fury.io/js/express-prom-bundle)\n\n# express prometheus bundle\n\nExpress middleware with popular prometheus metrics in one bundle. It's also compatible with koa v1 and v2 (see below).\n\nThis library uses **prom-client v15+** as a peer dependency. See: https://github.com/siimon/prom-client\n\nIf you need a support for older versions of prom-client (v12-v14), downgrade to express-prom-bundle v6.6.0\n\nIncluded metrics:\n\n* `up`: normally is just 1\n* `http_request_duration_seconds`: http latency histogram/summary labeled with `status_code`, `method` and `path`\n\n## Install\n\n```\nnpm install prom-client express-prom-bundle\n```\n\n## Sample Usage\n\n```javascript\nconst promBundle = require(\"express-prom-bundle\");\nconst app = require(\"express\")();\nconst metricsMiddleware = promBundle({includeMethod: true});\n\napp.use(metricsMiddleware);\napp.use(/* your middleware */);\napp.listen(3000);\n```\n\n* call your endpoints\n* see your metrics here: [http://localhost:3000/metrics](http://localhost:3000/metrics)\n\n**ALERT!**\n\nThe order in which the routes are registered is important, since\n**only the routes registered after the express-prom-bundle will be measured**\n\nYou can use this to your advantage to bypass some of the routes.\nSee the example below.\n\n## Options\n\nWhich labels to include in `http_request_duration_seconds` metric:\n\n* **includeStatusCode**: HTTP status code (200, 400, 404 etc.), default: **true**\n* **includeMethod**: HTTP method (GET, PUT, ...), default: **false**\n* **includePath**: URL path (see important details below), default: **false**\n* **customLabels**: an object containing extra labels, e.g. ```{project_name: 'hello_world'}```.\n  Most useful together with **transformLabels** callback, otherwise it's better to use native Prometheus relabeling.\n* **includeUp**: include an auxiliary \"up\"-metric which always returns 1, default: **true**\n* **metricsPath**: replace the `/metrics` route with a **regex** or exact **string**. Note: it is highly recommended to just stick to the default\n* **metricType**: histogram/summary selection. See more details below\n* **httpDurationMetricName**: Allows you change the name of HTTP duration metric, default: **`http_request_duration_seconds`**.\n\n### metricType option ###\n\nTwo metric types are supported for `http_request_duration_seconds` metric:\n* [histogram](https://prometheus.io/docs/concepts/metric_types/#histogram) (default)\n* [summary](https://prometheus.io/docs/concepts/metric_types/#summary)\n\nAdditional options for **histogram**:\n* **buckets**: buckets used for the `http_request_duration_seconds` histogram\n\nAdditional options for **summary**:\n* **percentiles**: percentiles used for `http_request_duration_seconds` summary\n* **ageBuckets**: ageBuckets configures how many buckets we have in our sliding window for the summary\n* **maxAgeSeconds**: the maxAgeSeconds will tell how old a bucket can be before it is reset\n* **pruneAgedBuckets**: When enabled, timed out buckets will be removed entirely. By default, buckets are reset to 0.\n\n### Transformation callbacks ###\n\n* **normalizePath**: `function(req)`  or `Array`\n  * if function is provided, then it should generate path value from express `req`\n  * if array is provided, then it should be an array of tuples `[regex, replacement]`. The `regex` can be a string and is automatically converted into JS regex.\n  * ... see more details in the section below\n* **urlValueParser**: options passed when instantiating [url-value-parser](https://github.com/disjunction/url-value-parser).\n  This is the easiest way to customize which parts of the URL should be replaced with \"#val\".\n  See the [docs](https://github.com/disjunction/url-value-parser) of url-value-parser module for details.\n* **formatStatusCode**: `function(res)` producing final status code from express `res` object, e.g. you can combine `200`, `201` and `204` to just `2xx`.\n* **transformLabels**: `function(labels, req, res)` transforms the **labels** object, e.g. setting dynamic values to **customLabels**\n* **urlPathReplacement**: replacement string for the values (default: \"#val\")\n\n### Other options ###\n\n* **autoregister**: if `/metrics` endpoint should be registered (default: **true**)\n* **promClient**: options for promClient startup, e.g. **collectDefaultMetrics**. This option was added\n  to keep `express-prom-bundle` runnable using confit (e.g. with kraken.js) without writing any JS code,\n  see [advanced example](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/advanced-example.js)\n* **promRegistry**: Optional `promClient.Registry` instance to attach metrics to. Defaults to global `promClient.register`.\n* **metricsApp**: Allows you to attach the metrics endpoint to a different express app. You probably want to use it in combination with `autoregister: false`.\n* **bypass**: An object that takes onRequest and onFinish callbacks that determines whether the given request should be excluded in the metrics. Default:\n\n  ```js\n  {\n    onRequest: (req) =\u003e false,\n    onFinish: (req, res) =\u003e false\n  }\n  ```\n\n  `onRequest` is run directly in the middleware chain, before the request is processed. `onFinish` is run after the request has been processed, and has access to the express response object in addition to the request object. Both callbacks are optional, and if one or both returns true the request is excluded.\n\n  As a shorthand, just the onRequest callback can be used instead of the object.\n\n\n### More details on includePath option\n\nLet's say you want to have  latency statistics by URL path,\ne.g. separate metrics for `/my-app/user/`, `/products/by-category` etc.\n\nJust taking `req.path` as a label value won't work as IDs are often part of the URL,\nlike `/user/12352/profile`. So what we actually need is a path template.\nThe module tries to figure out what parts of the path are values or IDs,\nand what is an actual path. The example mentioned before would be\nnormalized to `/user/#val/profile` and that will become the value for the label.\nThese conversions are handled by `normalizePath` function.\n\nYou can extend this magical behavior by providing\nadditional RegExp rules to be performed,\nor override `normalizePath` with your own function.\n\n#### Example 1 (add custom RegExp):\n\n```javascript\napp.use(promBundle({\n  normalizePath: [\n    // collect paths like \"/customer/johnbobson\" as just one \"/custom/#name\"\n    ['^/customer/.*', '/customer/#name'],\n\n    // collect paths like \"/bobjohnson/order-list\" as just one \"/#name/order-list\"\n    ['^.*/order-list', '/#name/order-list']\n  ],\n  urlValueParser: {\n    minHexLength: 5,\n    extraMasks: [\n      'ORD[0-9]{5,}' // replace strings like ORD1243423, ORD673562 as #val\n    ]\n  }\n}));\n```\n\n#### Example 2 (override normalizePath function):\n\n```javascript\napp.use(promBundle(/* options? */));\n\n// let's reuse the existing one and just add some\n// functionality on top\nconst originalNormalize = promBundle.normalizePath;\npromBundle.normalizePath = (req, opts) =\u003e {\n  const path = originalNormalize(req, opts);\n  // count all docs as one path, but /docs/login as a separate one\n  return (path.match(/^\\/docs/) \u0026\u0026 !path.match(/^\\/login/)) ? '/docs/*' : path;\n};\n```\n\nFor more details:\n * [url-value-parser](https://www.npmjs.com/package/url-value-parser) - magic behind automatic path normalization\n * [normalizePath.js](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/src/normalizePath.js) - source code for path processing\n\n\n#### Example 3 (return express route definition):\n\n```javascript\napp.use(promBundle(/* options? */));\n\npromBundle.normalizePath = (req, opts) =\u003e {\n  // Return the path of the express route (i.e. /v1/user/:id or /v1/timer/automated/:userid/:timerid\")\n  return req.route?.path ?? \"NULL\";\n};\n```\n\n## express example\n\nsetup std. metrics but exclude `up`-metric:\n\n```javascript\nconst express = require(\"express\");\nconst app = express();\nconst promBundle = require(\"express-prom-bundle\");\n\n// calls to this route will not appear in metrics\n// because it's applied before promBundle\napp.get(\"/status\", (req, res) =\u003e res.send(\"i am healthy\"));\n\n// register metrics collection for all routes\n// ... except those starting with /foo\napp.use(\"/((?!foo))*\", promBundle({includePath: true}));\n\n// this call will NOT appear in metrics,\n// because express will skip the metrics middleware\napp.get(\"/foo\", (req, res) =\u003e res.send(\"bar\"));\n\n// calls to this route will appear in metrics\napp.get(\"/hello\", (req, res) =\u003e res.send(\"ok\"));\n\napp.listen(3000);\n```\n\nSee an [advanced example on github](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/advanced-example.js)\n\n## koa v2 example\n\n```javascript\nconst promBundle = require(\"express-prom-bundle\");\nconst Koa = require(\"koa\");\nconst c2k = require(\"koa-connect\");\nconst metricsMiddleware = promBundle({/* options */ });\n\nconst app = new Koa();\n\napp.use(c2k(metricsMiddleware));\napp.use(/* your middleware */);\napp.listen(3000);\n```\n\n## using with cluster\n\nYou'll need to use an additional **clusterMetrics()** middleware.\n\nIn the example below the master process will expose an API with a single endpoint `/metrics`\nwhich returns an aggregate of all metrics from all the workers.\n\n``` javascript\nconst cluster = require('cluster');\nconst promBundle = require('express-prom-bundle');\nconst promClient = require('prom-client');\nconst numCPUs = Math.max(2, require('os').cpus().length);\nconst express = require('express');\n\nif (cluster.isMaster) {\n    for (let i = 1; i \u003c numCPUs; i++) {\n        cluster.fork();\n    }\n\n    const metricsApp = express();\n    metricsApp.use('/metrics', promBundle.clusterMetrics());\n    metricsApp.listen(9999);\n\n    console.log('cluster metrics listening on 9999');\n    console.log('call localhost:9999/metrics for aggregated metrics');\n} else {\n    new promClient.AggregatorRegistry();\n    const app = express();\n    app.use(promBundle({\n        autoregister: false, // disable /metrics for single workers\n        includeMethod: true\n    }));\n    app.use((req, res) =\u003e res.send(`hello from pid ${process.pid}\\n`));\n    app.listen(3000);\n    console.log(`worker ${process.pid} listening on 3000`);\n}\n```\n\n## using with kraken.js\n\nHere is meddleware config sample, which can be used in a standard **kraken.js** application.\nIn this case the stats for URI paths and HTTP methods are collected separately,\nwhile replacing all HEX values starting from 5 characters and all IP addresses in the path as #val.\n\n```json\n{\n  \"middleware\": {\n    \"expressPromBundle\": {\n      \"route\": \"/((?!status|favicon.ico|robots.txt))*\",\n      \"priority\": 0,\n      \"module\": {\n        \"name\": \"express-prom-bundle\",\n        \"arguments\": [\n          {\n            \"includeMethod\": true,\n            \"includePath\": true,\n            \"buckets\": [0.1, 1, 5],\n            \"promClient\": {\n              \"collectDefaultMetrics\": {\n              }\n            },\n            \"urlValueParser\": {\n              \"minHexLength\": 5,\n              \"extraMasks\": [\n                \"^[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+$\"\n              ]\n            }\n          }\n        ]\n      }\n    }\n  }\n}\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjochen-schweizer%2Fexpress-prom-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjochen-schweizer%2Fexpress-prom-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjochen-schweizer%2Fexpress-prom-bundle/lists"}