prouter

Fast, unopinionated, minimalist client side router library inspired in the simplicity and flexibility of express router.

Basically, give prouter a list of path expressions (routes) and a callback function (handler) for each one, and prouter will invoke the callbacks according to the activated path in the URL.

Why prouter?

Performance: fast and tiny size (currently under 5kb before gzipping) are both must to have to smoothly run in any mobile or desktop browser.

fast and tiny size (currently under 5kb before gzipping) are both must to have to smoothly run in any mobile or desktop browser. 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 achivable with prouter (see examples below).

do only one thing and do it well, routing! Guards? conditional execution? generic pre and post middlewares? all that and more is easily achivable with prouter (see examples below). Learn once: express router is very powerfull, flexible and simple, why not bringing a similar API to the frontend? Under the hood, prouter uses the same (wonderful) library that express for parsing routes path-to-regexp (so it allows the same flexibility to declare routes). Read more about the concept of middlewares here.

express router is very powerfull, flexible and simple, why not bringing a similar API to the frontend? Under the hood, prouter uses the same (wonderful) library that express for parsing routes path-to-regexp (so it allows the same flexibility to declare routes). Read more about the concept of middlewares here. Unobtrusive: it is designed from the beginning to play well with vanilla JavaScript or with any other library or framework.

it is designed from the beginning to play well with vanilla JavaScript or with any other library or framework. 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 API for routing.

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 API for routing. Unit tests for every feature are created.

Want to see it in action?

It is being used in this web app wherudo.com. There you can see how prouter is used to to do universal rendering for all the pages of that site.

Installation

npm install prouter --save yarn prouter --save < script src= " https://cdn.jsdelivr.net/npm/prouter@10.0.0/prouter.min.js " > < /script >

Examples

basic

import { browserRouter } from ' prouter ' ; const router = browserRouter ( ) ; router . use ( ' / ' , async ( req , resp ) => { const people = await personService . find ( ) ; const html = PersonListCmp ( people ) ; document . querySelector ( ' .router-outlet ' ) = html ; resp . end ( ) ; } ) . use ( ' /about ' , ( req , resp ) => { document . querySelector ( ' .router-outlet ' ) = ` <h1>Some static content for the About page.</h1> ` ; resp . end ( ) ; } ) ; router . listen ( ) ;

guard middleware which conditionally avoid executing next handlers and prevent changing the path in the URL

const prouter = require ( ' prouter ' ) ; const router = prouter . browserRouter ( { processHashChange : true } ) ; router . use ( ' * ' , ( req , resp , next ) => { const isAllowed = authService . validateHasAccessToUrl ( req . path ) ; if ( ! isAllowed ) { showAlert ( " You haven't rights to access the page: " + destPath ) ; resp . preventNavigation = true ; resp . end ( ) ; return ; } next ( ) ; } ) . use ( ' / ' , ( req , resp ) => { resp . end ( ) ; } ) . use ( ' /admin ' , ( req , resp ) => { resp . end ( ) ; } ) ; router . listen ( ) ; router . push ( ' /admin ' ) ;

run a generic middleware (for doing some generic stuff) after running specific handlers

import { browserRouter } from ' prouter ' ; const router = browserRouter ( ) ; router . use ( ' / ' , async ( req , resp , next ) => { const people = await personService . find ( ) ; const html = PersonListCmp ( people ) ; document . querySelector ( ' .router-outlet ' ) = html ; next ( ) ; } ) . use ( ' * ' , ( req , resp ) => { resp . end ( ) ; } ) ; router . listen ( ) ;

modularize your routing code in different files using Router Group

import { browserRouter , routerGroup } from ' prouter ' ; const productRouterGroup = routerGroup ( ) ; productRouterGroup . use ( ' / ' , ( req , resp ) => { resp . end ( ) ; } ) . use ( ' /create ' , ( req , resp ) => { resp . end ( ) ; } ) . use ( ' /:id( \\ d+) ' , ( req , resp ) => { const id = req . params . id ; resp . end ( ) ; } ) ; const router = browserRouter ( ) ; router . use ( ' * ' , ( req , resp , next ) => { console . log ( ' request info ' , req ) ; next ( ) ; } ) . use ( ' / ' , ( req , resp ) => { resp . end ( ) ; } ) . use ( ' /product ' , productRouterGroup ) ; router . listen ( ) ; router . push ( ' /product/123 ' ) ;

full example: modularized routing, generic pre handler acting as a guard, generic post handler.

import { browserRouter , routerGroup } from ' prouter ' ; const productRouterGroup = routerGroup ( ) ; productRouterGroup . use ( ' / ' , ( req , resp , next ) => { next ( ) ; } ) . use ( ' /create ' , ( req , resp , next ) => { next ( ) ; } ) . use ( ' /:id( \\ d+) ' , ( req , resp , next ) => { const id = req . params . id ; next ( ) ; } ) ; const router = browserRouter ( ) ; router . use ( ' * ' , ( req , resp , next ) => { const isAllowed = authService . validateHasAccessToUrl ( req . path ) ; if ( ! isAllowed ) { showAlert ( " You haven't rights to access the page: " + destPath ) ; resp . preventNavigation = true ; resp . end ( ) ; return ; } next ( ) ; } ) . use ( ' / ' , ( req , resp , next ) => { const doInfiniteScroll = ( ) => { } ; const onNavigation = ( navigationEvt ) => { console . log ( ' new path ' , navigationEvt . oldPath ) ; console . log ( ' old path ' , navigationEvt . newPath ) ; router . off ( ' navigation ' , onNavigation ) ; window . removeEventListener ( ' scroll ' , doInfiniteScroll ) ; } ; window . addEventListener ( ' scroll ' , doInfiniteScroll ) ; router . on ( ' navigation ' , onNavigation ) ; next ( ) ; } ) . use ( ' /login ' , ( ) => { openLoginModal ( ) ; resp . preventNavigation = true ; resp . end ( ) ; } ) . use ( ' /admin ' , ( req , resp , next ) => { next ( ) ; } ) . use ( ' /product ' , productRouterGroup ) . use ( ' * ' , ( req , res , next ) => { if ( req . listening ) { const title = inferTitleFromPath ( req . path , APP_TITLE ) ; updatePageTitle ( title ) ; } resp . end ( ) ; } ) ; router . listen ( ) ; export function isNavigationPath ( path : string ) { return !! path && ! path . startsWith ( ' javascript:void ' ) ; } export function isExternalPath ( path : string ) { return / ^ https ? : \/ \/ / . test ( path ) ; } export function isApplicationPath ( path : string ) { return isNavigationPath ( path ) && ! isExternalPath ( path ) ; } document . body . addEventListener ( ' click ' , ( evt ) => { const target = evt . target as Element ; let link : Element ; if ( target . nodeName === ' A ' ) { link = target ; } else { link = target . closest ( ' a ' ) ; if ( ! link ) { return ; } } const url = link . getAttribute ( ' href ' ) ; if ( ! isApplicationPath ( url ) ) { return ; } evt . preventDefault ( ) ; router . push ( url ) ; } ) ;

see more advanced usages in the unit tests.