npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

bluejacket

v0.0.11

Published

Simple ExpressJS Style Router for Isomorphic Apps

Downloads

12

Readme

What is BlueJacket?

BlueJacket is a tiny (~125LOC) router, that provides simple, Express Style routing for both the server and the browser. It uses the popular middleware style of the ExpressJS router with a few tweaks for better readabilty.

Important:

As of v0.0.6 BlueJacket comes with Typescript support. As of v0.0.10 Tyepscript support has been upgraded to provide mixin type definitions within handler context Usage Example:

import { BlueJacket } from 'bluejacket';
import fetch from 'node-fetch';

const router = new BlueJacket<{ html: string }>();

// Shared code
router.handle('/gists', async (context) => {
  const gists = await (await fetch('https://api.github.com/gists')).json();

  context.html = `<ul>
            ${gists
              .filter((gist) => {
                return gist.description && gist.html_url;
              })
              .map((gist) => {
                return `<li><a href=${gist.html_url}>${gist.description}</a></li>`;
              })
              .join('')}
        </ul>`;
});

// Browser Code
const context = router.resolve('/gists');
document.documentElement.innerHTML = context.html || ''; // Don't actually do this. innerHTML is BAD.

// Server Code
// app is an expressJS app
app.use(function(req, res, next) {
  const context = router.resolve(req.originalUrl);
  res.send(context.html);
});

Quickstart

To get started install BlueJacket.

yarn add bluejacket
npm install --save bluejacket

Let's create a route /gists, which will fetch a list of public gists from Github and display their titles.

const BlueJacket = require('bluejacket');
const fetch = require('node-fetch');

const router = new BlueJacket();

// Shared code
router.handle('/gists', async (context) => {
  const gists = await (await fetch('https://api.github.com/gists')).json();

  context.html = `<ul>
            ${gists
              .filter((gist) => gist.description && gist.html_url)
              .map((gist) => `<li><a href=${gist.html_url}>${gist.description}</a></li>`)
              .join('')}
        </ul>`;
});

// Browser Code
const context = router.resolve('/gists');
document.documentElement.innerHTML = context.html || ''; // Don't actually do this. innerHTML is BAD.

// Server Code
// app is an expressJS app
app.use(function(req, res, next) {
  const context = router.resolve(req.originalUrl);
  res.send(context.html);
});

API

BlueJacket

Main router class.

  1. Constructor
const BlueJacket = require('bluejacket');
const router = new BlueJacket(options);
  • options: [Optional] Options object with properties:
    • mixins - An object whose properties get merged into the context object passed to the request handlers.
      • Default : {}
    • caseSensitive - Specify if paths should be treated as case sensitive, while routing.
      • Default: false
    • strict - Specify if /path should be treated as distinct from /path/.
      • Default: false
    • instanceKey - A key used to uniquely identify this router instance. You can call the constructor again, with this instanceKey to get the same router instance. If not specified, instances are not tracked. Ex:

In /planets.js

const router = new BlueJacket({ caseSensitive: true, instanceKey: 'universe-router' });

In /stars.js

const router = new BlueJacket({ instanceKey: 'universe-router' });
router.opts.caseSensitive; //true
  1. handle method
router.handle(path, handler[, ...handlers]);
  • path - Optional string or regex denoting an expressJS style path. If none specified all paths will match against this handler.
  • handler - Function, Array of Functions, or destructured Array of Functions. Function can be async also. Ex:

If single function is provided, it will be triggered when the route matches.

router.handle('/planets', async (context) => {
  context.planets = await fetch('https://universe.example.com/planets').json();
});

If list of functions provided, each will be triggered in series:

router.handle(
  '/planets/named',
  async (context) => {
    context.planets = await fetch('https://universe.example.com/planets').json();
  },
  async (context) => {
    context.namedPlanets = context.planets.filter((planet) => planet.name);
  },
);

If any handler in the list is an Array of functions, all those functions will be triggered parallelly:

router.handle('/planets/named', [
  async (context) => {
    context.celestialBodies = (context.celestialBodies || []).concat(
      await fetch('https://universe.example.com/planets').json(),
    );
  },
  async (context) => {
    context.celestialBodies = (context.celestialBodies || []).concat(
      await fetch('https://universe.example.com/stars').json(),
    );
  },
]);

You can also mix and match above options as required:

router.handle(
  '/planets/named',
  [
    async (context) => {
      context.celestialBodies = (context.celestialBodies || []).concat(
        await fetch('https://universe.example.com/planets').json(),
      );
    },
    async (context) => {
      context.celestialBodies = (context.celestialBodies || []).concat(
        await fetch('https://universe.example.com/stars').json(),
      );
    },
  ],
  async (context) => {
    context.namedCelestialBodies = context.celestialBodies.filter((item) => item.name);
  },
);

If a handler function called in series throws, the remaining handlers will not be triggered. If a handler function called in parallel throws, the other parallel handlers will continue execution, but any subsequent serial handlers will not be triggered.

If you wish to skip the handlers after the current handler, you can throw the string route. Ex:

router.handle(
  '/planets/named',
  async (context) => {
    context.planets = await fetch('https://universe.example.com/planets').json();
    throw 'route';
  },
  async (context) => {
    // Will not be executed
    context.namedPlanets = context.planets.filter((planet) => planet.name);
  },
);

This will cause the resolution of the request to terminate.

Note that, when you throw route, it will be implicitly caught by BlueJacket. If you throw anything else, BlueJacket will catch and throw it again.

Context object

Each handler will be called with a context object. The context object is created at the start of the request life-cycle and then passed throught to subsequent handlers. If you wish to pass data between handlers, use the context object to do os. The context object has the following properties:

  • data: data object that the request resolution is requested with.
  • params: If you have any express style route params, these will be listed here in key value form. Regex capture groups will also appear here.
  • route: The actual route being resolved.
  • router: The router instance resolving this request.
  • Other properties from the mixins option defined on this router. If any mixin has the same name as one of the above properties it will be overwritten.
Important:

The context object passed to the handlers is the same object. So modifications to the context object will impact other routers. Keep this in mind, esp when modifying context object in parallelly defined handlers.

  1. resolve method
router.resolve(path, data);

This is the method that is called to actually resolve your request.

  • path - Request path to resolve.
  • data - Data to resolve with. This data is made available to your handlers as data property on the context object.

This method returns:

  • context - The shared context object that was passed through the request handlers list.

Usage in browser:

router.handle('/planets', async (context) => {
  context.data; // { named: true }
});
const context = router.resolve('/planets', { named: true });

Usage on server:

router.handle('/planets', async (context) => {
  context.data; // { named: true }
});

//Assuming you are using express and app is an express app
app.use(function(req, res, next) {
  const context = router.resolve(req.originalUrl, Object.assign({}, req.query, req.body));
});

Once all handlers have been executed, or an error thrown by any of the handlers, or any handler throws the string route, i.e. once the request resolution has either completed or been terminated, the context object will be made available as the return value of the resolve method.

You can then use this context object to perform any rendering that you might wish to do.

Important:

Please note that, by default, the router does not perform any kind of 'rendering'. It is up to you to render the response from the context object returned by resolve. Ex:

Browser rendering:

router.handle(...);
router.handle(...);
router.handle(...);

const context = router.resolve('/gists');
document.documentElement.innerHTML = context.html || ''; // Don't actually do this. innerHTML is BAD.

Server rendering with ExpressJS

router.handle(...);
router.handle(...);
router.handle(...);

// app is an expressJS app
app.use(function(req, res, next) {
    const context = router.resolve(req.originalUrl);
    res.send(`<html><head>Server Rendered</head><body>${context.html}</body></html>`);
});

And that's pretty much it! I'll add a few examples for things like rendering React shortly.