Skip to content
/ xin Public
forked from medleyjs/router

An enhanced javascript URL router

License

Notifications You must be signed in to change notification settings

xinkjs/xin

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@xinkjs/xin

An enhanced javascript URL router.

Like it's forked predecessor, xin is a building block on which you can create other router features. Its main purpose is to register and match routes. We've enhanced it by adding helper functions to extend the matcher route functionality and set/get global middleware.

xin (zen) is a shortened spelling of our API router xink (zinc). It also resembles a Chinese word that's translated as "heart-mind". This is fitting, since xin is the heart of the xink router.

Install

bun add @xinkjs/xin
deno add npm:@xinkjs/xin
[p]npm add @xinkjs/xin

QuickStart

import { Router } from '@xinkjs/xin'

const router = new Router()

router.get('/', () => { /* ... */})
router.get('/user', () => { /* ... */})
router.post('/user', () => { /* ... */})

Route conventions

  • A path must start with a /.
  • parameters - leading colon, e.g. /blog/:slug
  • matcher - equals sign separator followed by a word label, e.g. /user/:id=number
  • wildcard - trailing asterick, e.g. /thing/*

API

new Router(options?)

Constructs a new router instance.

  • options? object
  • Returns a class instance.
import { Router } from '@xinkjs/xin'

const router = new Router()

Options

  • storeFactory? function () => Object.create(null) - Used to create a route store when a route is registered. By default, returns {} with a null prototype (no access to methods like toString(), etc).

    import { Router } from '@xinkjs/xin'
    
    const router = new Router({
      storeFactory() {
        return { handlers: new Map(), middlewares: [] }
      }
    })
    
    const store = router.register('/')
    console.log(store) // { handlers: Map {}, middlewares: [] }

router.[method](path, handler)

Registers a route with the given HTTP method.

  • path string - The route path.
  • handler function - The route handler for the method.

xin supports these methods: get, post, put, patch, delete, head, options, fallback - fallback being returned from a router.find(path) call for any method not defined for the route.

import { Router } from '@xinkjs/xin'
const router = new Router()

router.get('/', () => new Response('Hello xin!'))

router.register(path)

An alternative to router.[method]().

Registers a route and returns it's store for further configuration. If the route has already been registered, it returns the existing route store. Use this method for full control over the store, which is typically used to store route handlers and other information.

  • path string - The route path.
  • Returns: object - The route store.
import { Router } from '@xinkjs/xin'
const router = new Router()

const store = router.register('/user/:id')
console.log(store) // [Object: null prototype] {}
store['GET'] = () => new Response('Hello xin!')

Route and Segment Types

  • Static - an exact match of the path.

    '/'
    '/about'
    '/api/login'
  • Specific - a mix of static content and a parameter.

    '/v:version' // Matches '/v1'
    '/on-:event'     // Matches '/on-click'
  • Matcher - applies a matcher function to the parameter.

    These segments reference a function which the parameter is tested against. So, you can think of the function as a validator for the parameter. For the purpose of the example below, the matcher function tests against a regular expression of /^\d+$/, so it has to be a number.

    '/api/user/:id=number' // Matches  '/api/user/42
    '/api/v:version=number' // Matches '/api/v1', but not '/api/vONE'

    xin provides three built-in matcher functions:

    • letter (param) => /^[a-z]+$/i.test(param)
    • number (param) => /^\d+$/.test(param)
    • word (param) => /^\w+$/.test(param)

    You can override the built-in matchers, or create your own, by registering them with setMatcher().

  • Dynamic - a segment that begins with a :, and will match anything up to the next / or to the end of the path. The text within these bounds defines the name of the parameter.

    '/user/:id' // Matches /user/42
    '/user/:id/post' // Matches /user/42/post
    '/user/:userId/post/:postId' // Matches /user/42/post/1
    import { Router } from '@xinkjs/xin'
    const router = new Router()
    
    router.get('/user/:id', () => {})
    
    const route = router.find('/user/42')
    console.log(route.params) // { id: '42' }

    If multiple routes have a parameter in the same part of the route, the parameter names must be the same. For example, registering the following two routes would be an error because the :id and :userId parameters conflict with each other:

    '/user/:id'
    '/user/:userId/post'
  • Wildcard - path ends with a *. This will match any characters in the rest of the path, including / characters or no characters. The wildcard value will be set in the route params object with '*' as the key.

    For example, the following route:

    '/static/*'

    will match all of these URLs:

    /static/
    /static/favicon.ico
    /static/js/main.js
    /static/css/vendor/bootstrap.css
    
    import { Router } from '@xinkjs/xin'
    const router = new Router()
    
    router.get('/static/*', () => {})
    
    let route = router.find('/static/favicon.ico')
    console.log(route.params) // { '*': 'favicon.ico' }
    
    route = router.find('/static/')
    console.log(route.params) // { '*': '' }

router.find(path)

Attempts to find a route.

  • path string - The route path to be matched.
  • Returns { store, params } | null
import { Router } from '@xinkjs/xin'
const router = new Router()

router.get('/', () => {})
router.get('/user', () => {})
router.post('/user', () => {})
router.get('/user/:id', () => {})
router.get('/static/*', () => {})

router.find('/')
// {
//   store: { GET: [Function] },
//   params: {}
// }

router.find('/user')
// {
//   store: { GET: [Function], POST: [Function] },
//   params: {}
// }

router.find('/user/42')
// {
//   store: { GET: [Function] },
//   params: { id: '42'}
// }

router.find('/static/js/common.js')
// {
//   store: { GET: [Function] },
//   params: { '*': 'js/common.js' }
// }

router.find('/not-defined')
// null

Path Match Order

The matching order goes from more specific to less specific.

  1. Static
  2. Specific
  3. Matcher
  4. Dynamic
  5. Wildcard

For example, an incoming path of "/static/v2" will be matched in the below order; where the regex for the "word" matcher is /^\w+$/.

'/static/v2'  //static
'/static/v:number'  //specific: because the "v" makes it partially static
'/static/:version=word'  //matcher: aka dynamic with a validator, so it is more specific than dynamic
'/static/:version'  //dynamic
'/static/*'  //wildcard

router.getTree()

Returns the entire route tree.

  • Returns: Node - The router's root node.
import { Router } from '@xinkjs/xin'
const router = new Router()

const tree = router.getTree()

router.setMatcher(type, matcher)

Sets a matcher, to be used with matcher route types.

  • type string - The name of the matcher.
  • matcher (param: string) => boolean - The function which is used to test params against.
import { Router } from '@xinkjs/xin'
const router = new Router()

router.setMatcher('fruit', (param) => {
  const fruits = new Set(['apple', 'orange', 'grape'])
  return fruits.has(param)
})

router.getMatcher()

Returns a specific matcher function.

  • Returns: Matcher | null
import { Router } from '@xinkjs/xin'
const router = new Router()

const matcher = router.getMatcher('word')

router.setMiddleware(function)

Sets the global middleware function. This must be a single function, but in theory it could handle multiple functions itself.

  • function () => Promise<Response> | Response - A global middleware function that runs on every request.
import { Router } from '@xinkjs/xin'
const router = new Router()

router.setMiddleware((req) => {
  return new Response('Hello, from middleware')
})

router.getMiddleware()

Returns the middleware function.

  • Returns: () => Promise<Response> | Response

router.setErrorHandler(function)

Sets a global error handling function.

  • function (error: unknown, event?: any) => Promise<Response | void> | Response | void - A global error handling function that runs when an unexpected error is thrown.
import { Router } from '@xinkjs/xin'
const router = new Router()

router.setErrorHandler((error, request) => {
  /* Handle error */
})

router.getErrorHandler()

Returns the error handler function.

  • Returns: (error: unknown, event?: any) => Promise<Response | void> | Response | void

About

An enhanced javascript URL router

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 100.0%