All Projects → berstend → Service Worker Router

berstend / Service Worker Router

➰ An elegant and fast URL router for service workers (and standalone use)

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to Service Worker Router

Apprun
AppRun is a JavaScript library for developing high-performance and reliable web applications using the elm inspired architecture, events and components.
Stars: ✭ 1,087 (+1452.86%)
Mutual labels:  router
Dorado
基于Netty4开发的简单、轻量级、高性能的的Http restful api server
Stars: ✭ 65 (-7.14%)
Mutual labels:  router
Koatty
Koa2 + Typescript = Koatty. Use Typescript's decorator implement IOC and AOP.
Stars: ✭ 67 (-4.29%)
Mutual labels:  router
Linker
A light weight URI routing framework for Android.
Stars: ✭ 57 (-18.57%)
Mutual labels:  router
Ng2 Breadcrumbs
A breadcrumb service for the Angular 7 router
Stars: ✭ 61 (-12.86%)
Mutual labels:  router
Flowa
🔥Service level control flow for Node.js
Stars: ✭ 66 (-5.71%)
Mutual labels:  router
Ska
Simple Karma Attack
Stars: ✭ 55 (-21.43%)
Mutual labels:  router
The forge
Our groundbreaking, lightning fast PWA CLI tool
Stars: ✭ 70 (+0%)
Mutual labels:  service-worker
Ngqp
Declaratively synchronize form controls with the URL
Stars: ✭ 65 (-7.14%)
Mutual labels:  router
Livebox 0day
Arcadyan ARV7519RW22-A-L T VR9 1.2 Multiple security vulnerabilities affecting latest firmware release on ORANGE Livebox modems.
Stars: ✭ 68 (-2.86%)
Mutual labels:  router
Kill Router
Ferramenta para quebrar senhas administrativas de roteadores Wireless, routers, switches e outras plataformas de gestão de serviços de rede autenticados.
Stars: ✭ 57 (-18.57%)
Mutual labels:  router
Vue Component Router
A component-based, declarative router for vue. Inspired by React Router 4
Stars: ✭ 61 (-12.86%)
Mutual labels:  router
Actions Openwrt K2p
Use Github Actions to automatically compile Lean's Modified Lede source for K2P
Stars: ✭ 67 (-4.29%)
Mutual labels:  router
Yinyue
🏖Version Of Progressive Web App ( Serverless )
Stars: ✭ 57 (-18.57%)
Mutual labels:  service-worker
Corenavigation
📱📲 Navigate between view controllers with ease. 💫 🔜 More stable version (written in Swift 5) coming soon.
Stars: ✭ 69 (-1.43%)
Mutual labels:  router
Dragon
⚡A powerful HTTP router and URL matcher for building Deno web servers.
Stars: ✭ 56 (-20%)
Mutual labels:  router
Route Recognizer
Recognizes URL patterns with support for dynamic and wildcard segments
Stars: ✭ 65 (-7.14%)
Mutual labels:  router
Parcel Plugin Sw Precache
A Parcel plugin for generating a service worker that precaches resources.
Stars: ✭ 70 (+0%)
Mutual labels:  service-worker
Sqlite Worker
A simple, and persistent, SQLite database for Web and Workers.
Stars: ✭ 70 (+0%)
Mutual labels:  service-worker
Route
原生 js 实现的轻量级路由,且页面跳转间有缓存功能。
Stars: ✭ 68 (-2.86%)
Mutual labels:  router

Please note ❇️

Although this router works fine I made a new one, based on experiences using it in production with Cloudflare Workers.

tiny-request-router is even smaller, even less opinionated and more flexible to use. It also uses 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.


service-worker-router

An elegant and fast URL router for service workers (and standalone use)

Yet another router? 😄

I was unable to find a modern router with the following features:

  • Framework agnostic and service worker support
    • 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.
    • The router is used in production with Cloudflare Workers.
  • TypeScript (and JavaScript) support
    • Even when not using TypeScript there's the benefit of better code editor tooling (improved IntelliSense) for the developer.
  • Match the path or the full URL
    • Most routers only support matching a /path, with service workers it's sometimes necessary to use the full URL as well.
  • Elegant pattern matching
    • Life's too short to debug regexes. :-)
  • Also: Lightweight (8KB, ~100 LOC), tested, supports tree shaking and ES modules

Installation

yarn add service-worker-router
# or
npm install --save service-worker-router

Usage

// TypeScript
import { Router, HandlerContext } from 'service-worker-router'

// Modern JavaScript, Babel, Webpack, Rollup, etc.
import { Router } from 'service-worker-router'

// Legacy JavaScript and Node.js
const { Router } = require('service-worker-router')

// Inside a web/service worker
importScripts('https://unpkg.com/service-worker-router')
const Router = self.ServiceWorkerRouter.Router

// HTML: Using ES modules
<script type="module">
  import { Router } from 'https://unpkg.com/service-worker-router/dist/router.browser.mjs';
</script>

// HTML: Oldschool
<script src="https://unpkg.com/service-worker-router"></script>
var Router = window.ServiceWorkerRouter.Router

URL polyfill

The router is making use of the WHATWG URL object. If your environment is Node < v8 or IE (see compat) you need to polyfill it before requiring/importing the router. By using polyfill.io the shim will only be loaded if the browser needs it.

// Add URL polyfill in Node.js < 8
// npm i --save universal-url
require('universal-url').shim()

// Add URL polyfill in workers
importScripts('https://cdn.polyfill.io/v2/polyfill.min.js?features=URL')

// Add URL polyfill in HTML scripts
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=URL"></script>

Example (service worker)

JavaScript

// Instantiate a new router
const router = new Router()

// Define user handler
const user = async ({ request, params }) => {
  const headers = new Headers({ 'x-user-id': params.id })
  const response = new Response(`Hello user with id ${params.id}.`, { headers })
  return response
}

// Define minimal ping handler
const ping = async () => new Response('pong')

// Define routes and their handlers
router.get('/user/:id', user)
router.all('/_ping', ping)

// Set up service worker event listener
addEventListener('fetch', event => {
  // Will test event.request against the defined routes
  // and use event.respondWith(handler) when a route matches
  router.handleEvent(event)
})

TypeScript

Same as the above but with optional types:

// Add 'webworker' to the lib property in your tsconfig.json
// also: https://github.com/Microsoft/TypeScript/issues/14877
declare const self: ServiceWorkerGlobalScope

// Instantiate a new router
const router = new Router()

// Define user handler
const user = async ({ request, params }: HandlerContext): Promise<Response> => {
  const headers = new Headers({ 'x-user-id': params.id })
  const response = new Response(`Hello user with id ${params.id}.`, { headers })
  return response
}

// Define minimal ping handler
const ping = async () => new Response('pong')

// Define routes and their handlers
router.get('/user/:id', user)
router.all('/_ping', ping)

// Set up service worker event listener
// To resolve 'FetchEvent' add 'webworker' to the lib property in your tsconfig.json
self.addEventListener('fetch', (event: FetchEvent) => {
  // Will test event.request against the defined routes
  // and use event.respondWith(handler) when a route matches
  router.handleEvent(event)
})

Example (standalone)

This router can be used on it's own using router.match, service worker usage is optional.

const router = new Router()

const user = async () => `Hey there!`
router.get('/user/:name', user)

router.match('/user/bob', 'GET')
// => { params: { name: 'bob' }, handler: [AsyncFunction: user],  url...

Patterns

The router is using the excellent url-pattern module (it's sole dependency).

Patterns can have optional segments and wildcards.

A route pattern can be a string or a UrlPattern instance, for greater flexibility and optional regex support.

Examples

// will match everything
router.all('*', handler)

// `id` value will be available in `params` in handler
router.all('/api/users/:id', handler)

// will only match exact path
router.all('/api/foo/', handler)

// will match longer paths as well
router.all('/api/foo/*', handler)

// will match with wildcard in between
router.all('/admin/*/user/*/tail', handler)

// use UrlPattern instance
router.all(new UrlPattern('/api/posts(/:id)'), handler)

URL matching

By 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.

Examples

// test against full url, not only path
router.post('(http(s)\\://)api.example.com/users(/:id)', handler, {
  matchUrl: true
})

// test against full url and extract segments
router.get('(http(s)\\://)(:subdomain.):domain.:tld(/*)', handler, {
  matchUrl: true
})

router.match('http://mail.google.com/mail', 'GET')
// => { params: {subdomain: 'mail', domain: 'google', tld: 'com', _: 'mail'}, handler: [AsyncFunction], ...

Refer to the url-pattern documentation and it's tests for more information and examples regarding pattern matching.

HTTP methods

To add a route, simply use one of the following methods. router.all will match any HTTP method.

  • router.all(pattern, handler, options)
  • router.get(pattern, handler, options)
  • router.post(pattern, handler, options)
  • router.put(pattern, handler, options)
  • router.patch(pattern, handler, options)
  • router.delete(pattern, handler, options)
  • router.head(pattern, handler, options)
  • router.options(pattern, handler, options)

The function signature is as follows:

pattern: string | UrlPattern
handler: HandlerFunction
options: RouteOptions = {}

The RouteOptions object is optional and can contain { matchUrl: boolean }.

All methods will return the router instance, for optional chaining.

Handler function

The handler function for a route is expected to be an async function (or Promise).

// See the HandlerContext interface below for all available params
const handler = async ({ request, params }) => {
  return new Response('Hello')
}

When used in a service worker context the handler must return a Response object, if the route matches.

When 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:

interface HandlerContext {
  params: any | null
  handler: HandlerFunction
  url: URL
  method: string
  route: Route
  request?: Request
  event?: FetchEvent
  ctx: any
}

API

Match

router.match(url: URL | string, method: string): MatchResult | null

Matches a supplied URL and HTTP method against the registered routes. url can be a string (path or full URL) or URL instance.

router.get('/user/:id', handler)

router.match('/user/1337', 'GET')
// => { params: { id: '1337' }, handler: [AsyncFunction: handler],  url...

The return value is a MatchResult object or null if no matching route was found.

interface MatchResult {
  params: any | null
  handler: HandlerFunction
  url: URL
  method: string
  route: Route
  request?: Request
  event?: FetchEvent
  ctx: any
}

router.matchRequest(request: Request): MatchResult | null

Will match a Request object (e.g. event.request) against the registered routes. Will return null or a MatchResult object.

addEventListener('fetch', event => {
  const match = router.matchRequest(event.request)
  console.log(match)
  // => { params: { user: 'bob' }, handler: [AsyncFunction: handler], ...
})

router.matchEvent(event: FetchEvent): MatchResult | null

Will match a FetchEvent object (e.g. event) against the registered routes. Will return null or a MatchResult object.

addEventListener('fetch', event => {
  const match = router.matchEvent(event)
  console.log(match)
  // => { params: { user: 'bob' }, handler: [AsyncFunction: handler], ...
})

Handle

router.handle(url: URL | string, method: string): HandleResult | null

Will match a string or URL instance against the registered routes and call it's handler function automatically (with HandlerContext).

const result = router.handle('/user/bob', 'GET')

Will return null or the matched route and handler promise as HandleResult:

interface HandleResult {
  match: MatchResult
  handlerPromise: HandlerPromise
}

router.handleRequest(request: Request): HandleResult | null

Will match a FetchEvent object against the registered routes and call it's handler function automatically (with HandlerContext).

addEventListener('fetch', event => {
  const result = router.handleRequest(event.request)
  if (result) {
    event.respondWith(result.handlerPromise)
  } else {
    console.log('No route matched.')
  }
})

Will return null or the matched route and handler promise as HandleResult.

router.handleEvent(event: FetchEvent): HandleResult | null

Will 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. :-)

addEventListener('fetch', event => {
  router.handleEvent(event)
})

Will return null or the matched route and handler promise as HandleResult.

Context (Since v1.7.5)

You 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.

Limitations

  • No middleware support
    • 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.

Examples

See also

  • workbox-router
  • sw-toolbox

License

MIT

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].