{"id":13473932,"url":"https://github.com/berstend/service-worker-router","last_synced_at":"2025-07-13T16:32:54.003Z","repository":{"id":32969203,"uuid":"148156195","full_name":"berstend/service-worker-router","owner":"berstend","description":"➰ An elegant and fast URL router for service workers (and standalone use)","archived":false,"fork":false,"pushed_at":"2022-12-03T12:48:51.000Z","size":1129,"stargazers_count":95,"open_issues_count":13,"forks_count":6,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-30T06:33:46.185Z","etag":null,"topics":["cloudflare-worker","nodejs","router","service-worker","typescript","url-router"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/berstend.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-10T13:02:33.000Z","updated_at":"2024-10-16T23:40:31.000Z","dependencies_parsed_at":"2023-01-14T22:52:23.594Z","dependency_job_id":null,"html_url":"https://github.com/berstend/service-worker-router","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berstend%2Fservice-worker-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berstend%2Fservice-worker-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berstend%2Fservice-worker-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berstend%2Fservice-worker-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/berstend","download_url":"https://codeload.github.com/berstend/service-worker-router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248618614,"owners_count":21134270,"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","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":["cloudflare-worker","nodejs","router","service-worker","typescript","url-router"],"created_at":"2024-07-31T16:01:08.117Z","updated_at":"2025-04-12T19:21:05.415Z","avatar_url":"https://github.com/berstend.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","Workers"],"sub_categories":["Recipes"],"readme":"# Please note :sparkle:\nAlthough this router works fine I [made a new one](https://github.com/berstend/tiny-request-router), based on experiences using it in production with Cloudflare Workers.\n\n[`tiny-request-router`](https://github.com/berstend/tiny-request-router) is even smaller, even less opinionated and more flexible to use. It also uses [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp) instead of `url-pattern`, as I found it more intuitive to use. I'd recommend using the new router for new projects.\n\n---\n\n\n# service-worker-router [![ ](https://travis-ci.org/berstend/service-worker-router.svg?branch=master)](https://travis-ci.org/berstend/service-worker-router) [![ ](https://img.shields.io/npm/v/service-worker-router.svg)](https://www.npmjs.com/package/service-worker-router)\n\n\u003e An elegant and fast URL router for service workers (and standalone use)\n\n\n## Yet another router? 😄\n\nI was unable to find a modern router with the following features:\n\n- **Framework agnostic** and [service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) support\n  - Most routers are intertwined with a specific web server or framework, this one is agnostic and can be used everywhere (Node.js, browsers, workers). See the [standalone](#example-standalone) example.\n  - The router is used in production with [Cloudflare Workers].\n- **TypeScript** (and JavaScript) **support**\n  - Even when not using TypeScript there's the benefit of better code editor tooling (improved IntelliSense) for the developer.\n- **Match the path or the full URL**\n  - Most routers only support matching a `/path`, with service workers it's sometimes necessary to use the full URL as well.\n- **Elegant pattern matching**\n  - Life's too short to debug regexes. :-)\n- Also: Lightweight (**8KB**, ~100 LOC), tested, supports [tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) and [ES modules](https://developers.google.com/web/fundamentals/primers/modules)\n\n## Installation\n\n```bash\nyarn add service-worker-router\n# or\nnpm install --save service-worker-router\n```\n\n## Usage\n\n```typescript\n// TypeScript\nimport { Router, HandlerContext } from 'service-worker-router'\n\n// Modern JavaScript, Babel, Webpack, Rollup, etc.\nimport { Router } from 'service-worker-router'\n\n// Legacy JavaScript and Node.js\nconst { Router } = require('service-worker-router')\n\n// Inside a web/service worker\nimportScripts('https://unpkg.com/service-worker-router')\nconst Router = self.ServiceWorkerRouter.Router\n\n// HTML: Using ES modules\n\u003cscript type=\"module\"\u003e\n  import { Router } from 'https://unpkg.com/service-worker-router/dist/router.browser.mjs';\n\u003c/script\u003e\n\n// HTML: Oldschool\n\u003cscript src=\"https://unpkg.com/service-worker-router\"\u003e\u003c/script\u003e\nvar Router = window.ServiceWorkerRouter.Router\n```\n\n#### `URL` polyfill\n\nThe router is making use of the WHATWG [URL] object. If your environment is **Node \u003c v8 or IE** (see [compat](https://caniuse.com/#feat=url)) you need to polyfill it before requiring/importing the router. By using [polyfill.io](https://polyfill.io/v2/docs/features/#URL) the shim will only be loaded if the browser needs it.\n\n```typescript\n// Add URL polyfill in Node.js \u003c 8\n// npm i --save universal-url\nrequire('universal-url').shim()\n\n// Add URL polyfill in workers\nimportScripts('https://cdn.polyfill.io/v2/polyfill.min.js?features=URL')\n\n// Add URL polyfill in HTML scripts\n\u003cscript src=\"https://cdn.polyfill.io/v2/polyfill.min.js?features=URL\"\u003e\u003c/script\u003e\n```\n\n## Example (service worker)\n\n### JavaScript\n\n```js\n// Instantiate a new router\nconst router = new Router()\n\n// Define user handler\nconst user = async ({ request, params }) =\u003e {\n  const headers = new Headers({ 'x-user-id': params.id })\n  const response = new Response(`Hello user with id ${params.id}.`, { headers })\n  return response\n}\n\n// Define minimal ping handler\nconst ping = async () =\u003e new Response('pong')\n\n// Define routes and their handlers\nrouter.get('/user/:id', user)\nrouter.all('/_ping', ping)\n\n// Set up service worker event listener\naddEventListener('fetch', event =\u003e {\n  // Will test event.request against the defined routes\n  // and use event.respondWith(handler) when a route matches\n  router.handleEvent(event)\n})\n```\n\n### TypeScript\n\nSame as the above but with optional types:\n\n```typescript\n// Add 'webworker' to the lib property in your tsconfig.json\n// also: https://github.com/Microsoft/TypeScript/issues/14877\ndeclare const self: ServiceWorkerGlobalScope\n\n// Instantiate a new router\nconst router = new Router()\n\n// Define user handler\nconst user = async ({ request, params }: HandlerContext): Promise\u003cResponse\u003e =\u003e {\n  const headers = new Headers({ 'x-user-id': params.id })\n  const response = new Response(`Hello user with id ${params.id}.`, { headers })\n  return response\n}\n\n// Define minimal ping handler\nconst ping = async () =\u003e new Response('pong')\n\n// Define routes and their handlers\nrouter.get('/user/:id', user)\nrouter.all('/_ping', ping)\n\n// Set up service worker event listener\n// To resolve 'FetchEvent' add 'webworker' to the lib property in your tsconfig.json\nself.addEventListener('fetch', (event: FetchEvent) =\u003e {\n  // Will test event.request against the defined routes\n  // and use event.respondWith(handler) when a route matches\n  router.handleEvent(event)\n})\n```\n\n## Example (standalone)\n\nThis router can be used on it's own using `router.match`, service worker usage is optional.\n\n```js\nconst router = new Router()\n\nconst user = async () =\u003e `Hey there!`\nrouter.get('/user/:name', user)\n\nrouter.match('/user/bob', 'GET')\n// =\u003e { params: { name: 'bob' }, handler: [AsyncFunction: user],  url...\n```\n\n## Patterns\n\nThe router is using the excellent [`url-pattern`](https://github.com/snd/url-pattern) module (it's sole dependency).\n\nPatterns can have optional segments and wildcards.\n\nA route pattern can be a string or a UrlPattern instance, for greater flexibility and optional regex support.\n\n#### Examples\n\n```js\n// will match everything\nrouter.all('*', handler)\n\n// `id` value will be available in `params` in handler\nrouter.all('/api/users/:id', handler)\n\n// will only match exact path\nrouter.all('/api/foo/', handler)\n\n// will match longer paths as well\nrouter.all('/api/foo/*', handler)\n\n// will match with wildcard in between\nrouter.all('/admin/*/user/*/tail', handler)\n\n// use UrlPattern instance\nrouter.all(new UrlPattern('/api/posts(/:id)'), handler)\n```\n\n### URL matching\n\nBy default the router will only match against the `/path` of a URL. To test against a full URL just add `{ matchUrl: true }` when adding a route.\n\n#### Examples\n\n```js\n// test against full url, not only path\nrouter.post('(http(s)\\\\://)api.example.com/users(/:id)', handler, {\n  matchUrl: true\n})\n\n// test against full url and extract segments\nrouter.get('(http(s)\\\\://)(:subdomain.):domain.:tld(/*)', handler, {\n  matchUrl: true\n})\n\nrouter.match('http://mail.google.com/mail', 'GET')\n// =\u003e { params: {subdomain: 'mail', domain: 'google', tld: 'com', _: 'mail'}, handler: [AsyncFunction], ...\n```\n\nRefer to the [`url-pattern`](https://github.com/snd/url-pattern) documentation and [it's tests](https://github.com/snd/url-pattern/blob/master/test/match-fixtures.coffee) for more information and examples regarding pattern matching.\n\n## HTTP methods\n\nTo add a route, simply use one of the following methods. `router.all` will match any HTTP method.\n\n- **router.all**(pattern, handler, options)\n- **router.get**(pattern, handler, options)\n- **router.post**(pattern, handler, options)\n- **router.put**(pattern, handler, options)\n- **router.patch**(pattern, handler, options)\n- **router.delete**(pattern, handler, options)\n- **router.head**(pattern, handler, options)\n- **router.options**(pattern, handler, options)\n\nThe function signature is as follows:\n\n```\npattern: string | UrlPattern\nhandler: HandlerFunction\noptions: RouteOptions = {}\n```\n\nThe `RouteOptions` object is optional and can contain `{ matchUrl: boolean }`.\n\nAll methods will return the router instance, for optional chaining.\n\n### Handler function\n\nThe handler function for a route is expected to be an `async` function (or `Promise`).\n\n```js\n// See the HandlerContext interface below for all available params\nconst handler = async ({ request, params }) =\u003e {\n  return new Response('Hello')\n}\n```\n\nWhen used in a service worker context the handler **must** return a [Response] object, if the route matches.\n\nWhen used in conjunction with helper methods like `router.handleRequest` and `router.handleEvent` the handler function will be called automatically with an object, containing the following signature:\n\n```typescript\ninterface HandlerContext {\n  params: any | null\n  handler: HandlerFunction\n  url: URL\n  method: string\n  route: Route\n  request?: Request\n  event?: FetchEvent\n  ctx: any\n}\n```\n\n## API\n\n### Match\n\n#### router.match(`url: URL | string, method: string`): `MatchResult | null`\n\nMatches a supplied URL and HTTP method against the registered routes. `url` can be a string (path or full URL) or [URL] instance.\n\n```js\nrouter.get('/user/:id', handler)\n\nrouter.match('/user/1337', 'GET')\n// =\u003e { params: { id: '1337' }, handler: [AsyncFunction: handler],  url...\n```\n\nThe return value is a `MatchResult` object or `null` if no matching route was found.\n\n```typescript\ninterface MatchResult {\n  params: any | null\n  handler: HandlerFunction\n  url: URL\n  method: string\n  route: Route\n  request?: Request\n  event?: FetchEvent\n  ctx: any\n}\n```\n\n#### router.matchRequest(`request: Request`): `MatchResult | null`\n\nWill match a [Request] object (e.g. `event.request`) against the registered routes. Will return `null` or a `MatchResult` object.\n\n```js\naddEventListener('fetch', event =\u003e {\n  const match = router.matchRequest(event.request)\n  console.log(match)\n  // =\u003e { params: { user: 'bob' }, handler: [AsyncFunction: handler], ...\n})\n```\n\n#### router.matchEvent(`event: FetchEvent`): `MatchResult | null`\n\nWill match a [FetchEvent] object (e.g. `event`) against the registered routes. Will return `null` or a `MatchResult` object.\n\n```js\naddEventListener('fetch', event =\u003e {\n  const match = router.matchEvent(event)\n  console.log(match)\n  // =\u003e { params: { user: 'bob' }, handler: [AsyncFunction: handler], ...\n})\n```\n\n### Handle\n\n#### router.handle(`url: URL | string, method: string`): `HandleResult | null`\n\nWill match a string or [URL] instance against the registered routes and call it's handler function automatically (with `HandlerContext`).\n\n```js\nconst result = router.handle('/user/bob', 'GET')\n```\n\nWill return `null` or the matched route and handler promise as `HandleResult`:\n\n```typescript\ninterface HandleResult {\n  match: MatchResult\n  handlerPromise: HandlerPromise\n}\n```\n\n#### router.handleRequest(`request: Request`): `HandleResult | null`\n\nWill match a [FetchEvent] object against the registered routes and call it's handler function automatically (with `HandlerContext`).\n\n```js\naddEventListener('fetch', event =\u003e {\n  const result = router.handleRequest(event.request)\n  if (result) {\n    event.respondWith(result.handlerPromise)\n  } else {\n    console.log('No route matched.')\n  }\n})\n```\n\nWill return `null` or the matched route and handler promise as `HandleResult`.\n\n#### router.handleEvent(`event: FetchEvent`): `HandleResult | null`\n\nWill match a [FetchEvent] object against the registered routes. If a route matches it's handler will be called automatically and passed to `event.respondWith(handler)`. If no route matches nothing happens. :-)\n\n```js\naddEventListener('fetch', event =\u003e {\n  router.handleEvent(event)\n})\n```\n\nWill return `null` or the matched route and handler promise as `HandleResult`.\n\n### Context (Since `v1.7.5`)\n\nYou can optionally add a context (`router.ctx = { foobar: 123 }`) to the router, which will be passed on to the handlers as part of `HandlerContext`. An example (also how to do this type safe) can be found in [the test fixture](./test/fixtures/sw.ts).\n\n## Limitations\n\n- No middleware support\n  - In service workers one needs to respond with a definitive [Response] object (when responding to a fetch event), so this paradigm doesn't really fit here.\n\n## Examples\n* [Error and NotFound/404 handling (Cloudflare Worker, TypeScript)](https://github.com/berstend/service-worker-router/issues/4#issuecomment-546683448)\n\n## See also\n\n- workbox-router\n- sw-toolbox\n\n## License\n\nMIT\n\n[url]: https://developer.mozilla.org/en-US/docs/Web/API/URL 'URL'\n[request]: https://developer.mozilla.org/en-US/docs/Web/API/Request 'Request'\n[response]: https://developer.mozilla.org/en-US/docs/Web/API/Response 'Response'\n[fetchevent]: https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent 'FetchEvent'\n[cloudflare workers]: https://www.cloudflare.com/products/cloudflare-workers/ 'Cloudflare Workers'\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberstend%2Fservice-worker-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fberstend%2Fservice-worker-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberstend%2Fservice-worker-router/lists"}