{"id":19675672,"url":"https://github.com/kyle-west/dragon-router","last_synced_at":"2025-04-29T02:30:41.579Z","repository":{"id":116727896,"uuid":"151654182","full_name":"kyle-west/dragon-router","owner":"kyle-west","description":":dragon: An ExpressJS-like client side router built from the ground up on debuggability and simplicity","archived":false,"fork":false,"pushed_at":"2020-04-27T23:44:08.000Z","size":273,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-22T23:04:08.462Z","etag":null,"topics":["client-router","debugging","express-like","middleware","pwa-router","react-router","router"],"latest_commit_sha":null,"homepage":"https://npmjs.com/dragon-router","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/kyle-west.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":"2018-10-05T00:52:39.000Z","updated_at":"2022-02-10T16:25:31.000Z","dependencies_parsed_at":"2023-04-05T20:04:49.130Z","dependency_job_id":null,"html_url":"https://github.com/kyle-west/dragon-router","commit_stats":{"total_commits":37,"total_committers":3,"mean_commits":"12.333333333333334","dds":0.5135135135135135,"last_synced_commit":"65a2cf35c0164df3ae271dc8c49f22b089e3c3c8"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyle-west%2Fdragon-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyle-west%2Fdragon-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyle-west%2Fdragon-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyle-west%2Fdragon-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kyle-west","download_url":"https://codeload.github.com/kyle-west/dragon-router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251420887,"owners_count":21586697,"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":["client-router","debugging","express-like","middleware","pwa-router","react-router","router"],"created_at":"2024-11-11T17:25:29.334Z","updated_at":"2025-04-29T02:30:41.573Z","avatar_url":"https://github.com/kyle-west.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🐉 Dragon Router\n\n[![](https://travis-ci.com/kyle-west/dragon-router.svg?branch=master)](https://travis-ci.com/kyle-west/dragon-router/branches)\n[![](https://img.shields.io/npm/v/dragon-router.svg)](https://www.npmjs.com/package/dragon-router)\n[![](https://img.shields.io/npm/dw/dragon-router.svg)](https://www.npmjs.com/package/dragon-router)\n\nDragon Router is an ExpressJS-like client side router built from the ground up \non [debuggability](#debugging) and [simplicity](#derived-subpaths).\n\nIt uses the browser's `history` API to control the pushing and popping of page navigation;\noverwriting the need for a full page refresh on user navigation. Add it to your project with NPM:\n\n```sh\nnpm install --save dragon-router\n```\n\n### Try the demo: \n\n[![Edit dragon-router-demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/dragon-router-demo-7os1x?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n# Setup and usage\n\n### ES6\n\n`Router` is an es6 class. Import it like you would any other module. After \nsetting up your routes (See [below](#route-matching)), register the router on the\nwindow. \n\n```js\nimport { Router } from 'dragon-router';\nconst router = new Router();\n\n... // add routing rules here\n\nrouter.registerOn(window);\nrouter.start() // run this function after you have established routing rules, so that the router knows it should immediately apply them\n```\n\n### CommonJS\n\nIf you are using CommonJS, you may import the proper version from the `/dist` folder.\n\n```js\nconst { Router } = require('dragon-router/dist/dragon-router.min.js')\n```\n\n### Native Browser Sourcing\n\nLikewise, you can include it in your HTML from a `script` tag.\n\n```html\n\u003c!-- Globally Registered --\u003e\n\u003cscript src=\"/path/to/dragon-router/dist/dragon-router.min.js\"\u003e\u003c/script\u003e\n\nOR\n\n\u003c!-- ES6 Module Imports --\u003e\n\u003cscript type=\"module\" src=\"/path/to/dragon-router/dist/dragon-router.module.js\"\u003e\u003c/script\u003e\n```\n\n## Options\n\n```js\nlet options = {\n  basePath: '/my-app/base/route',    // mount the router off of a specific path     [default is '/']\n  routerId: 'my-cool-dragon-router', // unique identifier between apps              [default is a random number]\n  registerOn: window,                // bind to the client's browser immediately    [if not given, `router.registerOn(...)` must be called separately]\n  debug: true                        // show additional logging info                [default is false]\n}\n\nconst router = new Router(options);\n```\n\n# `Router.use()`\n\nThe `.use()` method allows us to apply matchers or behaviors to the routing. \n\n## Route matching\n\nRoute matching follows a similar pattern as Express. You can match with literal paths\nor parameterized paths (which populate the `Context` with parameters).\n\n```js\n// render a page on a literal path matching\nrouter.use('/about', renderYourAboutPageCB);\n```\n\n```js\nrouter.use('/:section/:subSection', (context) =\u003e {\n  \n  // prefixing a path section with ':' will name that section in `context.params` \n  let section = context.params.section;\n  let subSection = context.params.subSection;\n\n  // now you can use the grepped data to apply on your app.\n  renderYourPageCB(section, subSection);\n\n});\n```\n\nYou can append a parameter declaration with `(` `)` to specify a regex pattern\nto enforce a match.\n\n```js\nrouter.use('/:section(home|about)/:subSection', (context) =\u003e {\n\n  // now, the path will only ever match if the `section` is 'home' or 'about'\n  let section = context.params.section;\n  \n  ...\n});\n```\n\nAdditionally, you may apply an array of matchers to a given handler.\n\n```js\nrouter.use(['/home/:subSection', '/about/:subSection'], (context) =\u003e {\n  // your code here\n});\n```\n\nFull `RegExp` matchers are also supported. _(Note that these do not get parameterized, unlike the string matchers mentioned above)_\n\n```js\nrouter.use(/^\\/some\\/fancy\\/regular\\/expression$/, (context) =\u003e {\n  // your code here\n});\n```\n\n\n### Optional Subpaths\n\nAdditional syntax of matchers includes `*` and `?` postfixes to sections.\n\nThe `*` postfix (e.g. `/your/route*`) will match any incoming route that is \nprefixed with the text before the `*` character.\n\nThe `?` postfix (e.g. `/your/:route?`) allows that section of the route to be \noptional. If you want to have the router automatically populate an optional section \nwith data, see [Derived Subpaths](#derived-subpaths) below.\n\n\n## Derived Subpaths\n\nA `DerivedSubpath` allows for a route to specify default values for an optional path.\nThese are derived from a given callback. The callback for the DerivedSubpath can return \nan `async` object or a `String`. This is especially useful for automatically redirecting \nto fully qualified paths in your app. \n\nHere is an example:\n\n```js\nlet defaultSection = new DerivedSubpath('section', (context) =\u003e {\n  return 'main'; // or whatever you need to do to compute the default `section`\n})\nrouter.use(defaultSection);\n\n...\n\n// prefixing a parameter with '$' tells the router we want to use a DerivedSubpath\nrouter.use('/page/$:section(main|about|contact)', renderPageSectionCB)\n```\n\nIn this example, the `section` parameter will always be defined when `renderPageSectionCB` is ran.\nIf the user goes to `/page`, they will be redirected to `/page/main` by the router.\n\nBy this principle, the following two blocks are functionally equivalent:\n\n```js\nrouter.use('/page', (context) =\u003e {\n  router.redirect(`/page/main`);\n})\nrouter.use('/page/:section(main|about|contact)', (context) =\u003e {\n  router.redirect(`/page/${context.params.section}/default`);\n});\nrouter.use('/page/:section(main|about|contact)/:subsection(default|other)', renderPageCB);\n```\n\nand \n\n```js\nrouter.use(new DerivedSubpath('section', () =\u003e 'main'));\nrouter.use(new DerivedSubpath('subsection', () =\u003e 'default'));\nrouter.use('/page/$:section(main|about|contact)/$:subsection(default|other)', renderPageCB);\n```\n\nThe latter being easier for the developer to hold in their mental model of the routing.\n\n## Middleware\n\nMiddleware is a pipeline of functions that get applied when a matching route is \nrendered. A middleware function takes two parameters, `context`, and `next`. The `next`\nargument is a callback that invokes the next middleware in the pipeline. Naturally, \n`next()` should not be called in the last function of the pipeline.\n\n```js\nlet loggingMiddleware = (context, next) =\u003e {\n  console.log('[Router]: navigating to ', context.path)\n  next(); \n}\n\n...\n\nrouter.use('/:view', loggingMiddleware, renderYourViewCB);\n```\n\nAlternatively, functions that are given to `Router.use(...)` without a matcher are \ntreated as global middleware, and ran for every route.\n\n```js\nrouter.use(loggingMiddleware);\nrouter.use(someOtherMiddleware1, someOtherMiddleware2); // accepts multiple middleware in one `use` statement\n```\n\n\n## `RouteHandler`\n\nIf you wish to make a particular handler reusable, you may form it as a `RouteHandler` for your convenience.\n\nThe constructor of `RouteHandler` takes two arguments: \n- [Matchers](#route-matching)\n- And array of [middleware handlers](#middleware)\n\n```js\nlet handler = new RouteHandler('/demo', [middleware1, middleware2, (context) =\u003e {\n  // your handle here\n}]);\n\nrouter.use(handler);\n```\n\n# Removing the Router\n\nIn the event that you wish to remove the router from your application, you will need to first unregister it before deleting. \n\n```js\nrouter.unregister()\nrouter = undefined; // or whatever your flavor of deleting objects in JS\n```\n\n# Debugging\n\nDragon Router was built with debuggability in mind. In the console, you have access \nto valuable information that can help you understand what the router is doing.\n\n```\n\u003e window.attachedRouter\n```\nThis feature allows us to poke into the internals of the router and understand \ninformation about the callback `registrar` and what paths are handled by which callbacks.\n\n![](./demo/misc/window.attachedRouter.png)\n\nAdditionally, when you add the option `{ debug: true }` to the router, we get helpful output from the \nrouter live as the user navigates around the page.\n\n```js\nconst router = new Router({ debug: true });\n```\n\n![](./demo/misc/demo.equals.true.png)\n\n\n# Using with ReactJS\n\nWith [Hooks](https://reactjs.org/docs/hooks-intro.html), integration into ReactJS is pretty simple. Below is an example on how to do so.\n\n```jsx\n// Import our router and the pages we want to client render\nimport React from 'react'\nimport { Router } from 'dragon-router'\nimport { DefaultPage, ExamplePage } from '../my-client-rendered-pages'\n\n// This function sets up the router to select a page to render based off of the current path\nfunction attachRouter (updatePage) {\n  return () =\u003e {\n    const router = new Router({ registerOn: window })\n    \n    // set up routing rules\n    router.use('/example', ctx =\u003e updatePage(\u003cExamplePage context={ctx} /\u003e))\n    router.use('*', ctx =\u003e updatePage(\u003cDefaultPage context={ctx} /\u003e))\n    router.start()\n\n    // cleanup callback\n    return () =\u003e router.unregister()\n  }\n}\n\n// Now our app conditionally renders pages based off of the route we are on\nexport default function App ({}) {\n  const [page, updatePage] = React.useState(\u003cDefaultPage /\u003e)\n  React.useEffect(attachRouter(updatePage), [])\n\n  return \u003cdiv\u003e{page}\u003c/div\u003e\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyle-west%2Fdragon-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyle-west%2Fdragon-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyle-west%2Fdragon-router/lists"}