{"id":15606675,"url":"https://github.com/rogerpadilla/prouter","last_synced_at":"2025-12-12T03:23:20.574Z","repository":{"id":30502221,"uuid":"34056564","full_name":"rogerpadilla/prouter","owner":"rogerpadilla","description":"Fast, unopinionated, minimalist client-side routing library inspired by the simplicity and flexibility of express middlewares","archived":false,"fork":false,"pushed_at":"2024-10-30T11:32:41.000Z","size":1740,"stargazers_count":52,"open_issues_count":2,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-11-19T04:19:02.657Z","etag":null,"topics":["browser","frontend","library","mobile","router","routing","web"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/rogerpadilla.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":"2015-04-16T13:13:08.000Z","updated_at":"2025-06-17T08:18:58.000Z","dependencies_parsed_at":"2023-07-13T14:54:33.186Z","dependency_job_id":"854ab410-448f-48a0-8bdb-9bacb8bc8242","html_url":"https://github.com/rogerpadilla/prouter","commit_stats":{"total_commits":535,"total_committers":10,"mean_commits":53.5,"dds":"0.34205607476635513","last_synced_commit":"50bfb1e39f4b2e8c09fe27654e6500bbabc313f4"},"previous_names":["rogerpadilla/easy-router"],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/rogerpadilla/prouter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogerpadilla%2Fprouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogerpadilla%2Fprouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogerpadilla%2Fprouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogerpadilla%2Fprouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rogerpadilla","download_url":"https://codeload.github.com/rogerpadilla/prouter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogerpadilla%2Fprouter/sbom","scorecard":{"id":782775,"data":{"date":"2025-08-11","repo":{"name":"github.com/rogerpadilla/prouter","commit":"6f35fd64890f41e5798c2e317781750bbdcf3b2c"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/28 approved changesets -- score normalized to 0","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":"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":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"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":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/rogerpadilla/prouter/tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/rogerpadilla/prouter/tests.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned"],"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"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 'main'"],"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 2 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":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","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-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3"],"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-23T05:17:54.600Z","repository_id":30502221,"created_at":"2025-08-23T05:17:54.601Z","updated_at":"2025-08-23T05:17:54.601Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27675499,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-12T02:00:06.775Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["browser","frontend","library","mobile","router","routing","web"],"created_at":"2024-10-03T04:41:32.933Z","updated_at":"2025-12-12T03:23:20.553Z","avatar_url":"https://github.com/rogerpadilla.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# prouter\n\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/rogerpadilla/prouter/blob/main/LICENSE)\n[![tests](https://github.com/rogerpadilla/prouter/actions/workflows/tests.yml/badge.svg)](https://github.com/rogerpadilla/prouter)\n[![coverage status](https://coveralls.io/repos/github/rogerpadilla/prouter/badge.svg)](https://coveralls.io/github/rogerpadilla/prouter)\n[![npm version](https://badge.fury.io/js/prouter.svg)](https://www.npmjs.com/prouter)\n\nFast, unopinionated, minimalist client-side routing library inspired by the simplicity and flexibility of [express middlewares](https://expressjs.com/en/guide/writing-middleware.html).\n\nEssentially, give `prouter` a list of path expressions (routes) and a callback function (handler) for each one, and `prouter` will automatically invoke these callbacks according to the active path in the URL.\n\n## Why prouter?\n\n- **Performance:** [fast](https://github.com/rogerpadilla/prouter/blob/master/src/browser-router.spec.ts#L7) and tiny size (currently under 5kb before gzipping) are both must-haves to smoothly run in any mobile or desktop browser.\n- **KISS principle everywhere:** do only one thing and do it well, routing! Guards? conditional execution? generic pre and post middlewares? all that and more is easily achievable with prouter (see examples below).\n- **Learn once:** express router is very powerful, flexible, and simple, why not bring a similar API to the frontend? Under the hood, prouter uses the same (wonderful) library that `express` for parsing routes [path-to-regexp](https://github.com/pillarjs/path-to-regexp) (so it allows the same flexibility to declare routes). Read more about the concept of middlewares [here](https://expressjs.com/en/guide/writing-middleware.html).\n- **Unobtrusive:** it is designed from the beginning to play well with vanilla JavaScript or with any other library or framework.\n- **Forward-thinking:** written in TypeScript for the future and transpiled to es5 with UMD format for the present... thus it transparently supports any module style: es6, commonJS, AMD. By default, prouter uses the modern [history](https://developer.mozilla.org/en-US/docs/Web/API/History_API) API for routing.\n- Unit tests for every feature are created.\n\nDo you like Prouter? [please give it a 🌟](https://github.com/rogerpadilla/prouter)\n\n## Installation\n\n```bash\n# With NPM\nnpm install prouter --save\n\n# Or with Yarn\nyarn prouter --save\n\n# Or just include it using a 'script' tag in your HTML file\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/prouter/prouter.min.js\"\u003e\u003c/script\u003e\n```\n\n## Examples\n\n### basic\n\n```js\n// Using es6 modules\nimport { browserRouter } from 'prouter';\n\n// Instantiate the router\nconst router = browserRouter();\n\n// Declare the paths and its respective handlers\nrouter\n  .use('/', async (req, resp) =\u003e {\n    const people = await personService.find();\n    const html = PersonListCmp(people);\n    document.querySelector('.router-outlet') = html;\n    // end the request-response cycle\n    resp.end();\n  })\n  .use('/about', (req, resp) =\u003e {\n    document.querySelector('.router-outlet') =\n      `\u003ch1\u003eSome static content for the About page.\u003c/h1\u003e`;\n    // end the request-response cycle\n    resp.end();\n  });\n\n// start listening for navigation events\nrouter.listen();\n```\n\n### guard middleware which conditionally avoid executing next handlers and prevent changing the path in the URL\n\n```js\n// Using commonJs modules\nconst prouter = require('prouter');\n\n// Instantiate the router\nconst router = prouter.browserRouter({\n  processHashChange: true // this allows to process 'hash' changes in the URL.\n});\n\n// Declare the paths and its respective handlers\nrouter\n  .use('*', (req, resp, next) =\u003e {\n    // this handler will run for any routing event, before any other handlers\n\n    const isAllowed = authService.validateHasAccessToUrl(req.path);\n\n    if (!isAllowed) {\n      showAlert(\"You haven't rights to access the page: \" + destPath);\n      // end the request-response cycle, avoid executing other handlers\n      // and prevent changing the path in the URL.\n      resp.preventNavigation = true;\n      resp.end();\n      return;\n    }\n\n    // pass control to the next handler\n    next();\n  })\n  .use('/', (req, resp) =\u003e {\n    // do some stuff...\n    // and end the request-response cycle\n    resp.end();\n  })\n  .use('/admin', (req, resp) =\u003e {\n    // do some stuff...\n    // and end the request-response cycle\n    resp.end();\n  });\n\n// start listening for navigation events\nrouter.listen();\n\n// programmatically try to navigate to any route in your router\nrouter.push('/admin');\n```\n\n### run a generic middleware (for doing some generic stuff) after running specific handlers\n\n```js\nimport { browserRouter } from 'prouter';\n\n// Instantiate the router\nconst router = browserRouter();\n\n// Declare the paths and its respective handlers\nrouter\n  .use('/', async (req, resp, next) =\u003e {\n    const people = await personService.find();\n    const html = PersonListCmp(people);\n    document.querySelector('.router-outlet') = html;\n    // pass control to the next handler\n    next();\n  })\n  .use('*', (req, resp) =\u003e {\n    // do some (generic) stuff...\n    // and end the request-response cycle\n    resp.end();\n  });\n\n// start listening for navigation events\nrouter.listen();\n```\n\n### modularize your routing code in different files using Router Group\n\n```js\nimport { browserRouter, routerGroup } from 'prouter';\n\n// this can be in a different file for modularization of the routes,\n// and then import it in your main routes file and mount it.\nconst productRouterGroup = routerGroup();\n\nproductRouterGroup\n  .use('/', (req, resp) =\u003e {\n    // do some stuff...\n    // and end the request-response cycle\n    resp.end();\n  })\n  .use('/create', (req, resp) =\u003e {\n    // do some stuff...\n    // and end the request-response cycle\n    resp.end();\n  })\n  .use('/:id(\\\\d+)', (req, resp) =\u003e {\n    const id = req.params.id;\n    // do some stuff with the 'id'...\n    // and end the request-response cycle\n    resp.end();\n  });\n\n// Instantiate the router\nconst router = browserRouter();\n\n// Declare the paths and its respective handlers\nrouter\n  .use('*', (req, resp, next) =\u003e {\n    // this handler will run for any routing event, before any other handlers\n    console.log('request info', req);\n    // pass control to the next handler\n    next();\n  })\n  .use('/', (req, resp) =\u003e {\n    // do some stuff...\n    // and end the request-response cycle\n    resp.end();\n  })\n  // mount the product's group of handlers using this base path\n  .use('/product', productRouterGroup);\n\n// start listening for the routing\nrouter.listen();\n\n// programmatically navigate to the detail of the product with this ID\nrouter.push('/product/123');\n```\n\n### full example: modularized routing, generic pre handler acting as a guard, generic post handler\n\n```js\nimport { browserRouter, routerGroup } from 'prouter';\n\n// this can be in a different file for modularization of the routes,\n// and then import it in your main routes file and mount it.\nconst productRouterGroup = routerGroup();\n\nproductRouterGroup\n  .use('/', (req, resp, next) =\u003e {\n    // do some stuff...\n    // and pass control to the next handler\n    next();\n  })\n  .use('/create', (req, resp, next) =\u003e {\n    // do some stuff...\n    // and pass control to the next handler\n    next();\n  })\n  .use('/:id(\\\\d+)', (req, resp, next) =\u003e {\n    const id = req.params.id;\n    // do some stuff with the 'id'...\n    // and pass control to the next handler\n    next();\n  });\n\n// Instantiate the router\nconst router = browserRouter();\n\n// Declare the paths and its respective handlers\nrouter\n  .use('*', (req, resp, next) =\u003e {\n\n    // this handler will run for any routing event, before any other handlers\n\n    const isAllowed = authService.validateHasAccessToUrl(req.path);\n\n    if (!isAllowed) {\n      showAlert(\"You haven't rights to access the page: \" + destPath);\n      // end the request-response cycle, avoid executing next handlers\n      // and prevent changing the path in the URL.\n      resp.preventNavigation = true;\n      resp.end();\n      return;\n    }\n\n    // pass control to the next handler\n    next();\n  })\n  .use('/', (req, resp, next) =\u003e {\n\n    const doInfiniteScroll = () =\u003e {\n      // do infinite scroll ...\n    };\n\n    const onNavigation = (navigationEvt) =\u003e {\n      console.log('new path', navigationEvt.oldPath);\n      console.log('old path', navigationEvt.newPath);\n      // if navigating, then remove the listener for the window.scroll.\n      router.off('navigation', onNavigation);\n      window.removeEventListener('scroll', doInfiniteScroll);\n    };\n\n    window.addEventListener('scroll', doInfiniteScroll);\n\n    // subscribe to the navigation event\n    router.on('navigation', onNavigation);\n\n    // and pass control to the next handler\n    next();\n  })\n  .use('/login', () =\u003e {\n    openLoginModal();\n    // as this route opens a modal, we would want to prevent navigation in this handler,\n    // so end the request-response cycle, avoid executing next handlers\n    // and prevent changing the path in the URL.\n    resp.preventNavigation = true;\n    resp.end();\n  })\n  .use('/admin', (req, resp, next) =\u003e {\n    // do some stuff...\n    // and pass control to the next handler\n    next();\n  })\n  // mount the product's group of handlers using this base path\n  .use('/product', productRouterGroup)\n  .use('*', (req, res, next) =\u003e {\n\n    // this handler will run for any routing event, after the other handlers\n\n    // req.listening will be true when this callback was called due to a\n    // client-side navigation (useful to differentiate client-side vs\n    // server-side rendering - when using a mix of both SSR and CSR)\n    if (req.listening) {\n      const title = inferTitleFromPath(req.path, APP_TITLE);\n      updatePageTitle(title);\n    }\n\n    // end the request-response cycle\n    resp.end();\n  });\n\n// start listening for the routing\nrouter.listen();\n\n\n// the below code is an example about how you could capture clicks on links,\n// and accordingly, trigger routing navigation in your app\n// (typically, you would put it in a separated file)\n\nexport function isNavigationPath(path: string) {\n  return !!path \u0026\u0026 !path.startsWith('javascript:void');\n}\n\nexport function isExternalPath(path: string) {\n  return /^https?:\\/\\//.test(path);\n}\n\nexport function isApplicationPath(path: string) {\n  return isNavigationPath(path) \u0026\u0026 !isExternalPath(path);\n}\n\ndocument.body.addEventListener('click', (evt) =\u003e {\n\n    const target = evt.target as Element;\n    let link: Element;\n\n    if (target.nodeName === 'A') {\n      link = target;\n    } else {\n      link = target.closest('a');\n      if (!link) {\n        return;\n      }\n    }\n\n    const url = link.getAttribute('href');\n\n    // do nothing if it is not an app's internal link\n    if (!isApplicationPath(url)) {\n      return;\n    }\n\n    // avoid the default browser's behaviour when clicking on a link\n    // (i.e. do not reload the page).\n    evt.preventDefault();\n\n    // it is a normal app's link, so trigger the routing navigation\n    router.push(url);\n  });\n```\n\n### see more advanced usages in the [unit tests.](https://github.com/rogerpadilla/prouter/blob/master/src/browser-router.spec.ts)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frogerpadilla%2Fprouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frogerpadilla%2Fprouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frogerpadilla%2Fprouter/lists"}