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 🙏

© 2024 – Pkg Stats / Ryan Hefner

koa-wormhole

v0.5.0

Published

A http request router for Koa

Downloads

13

Readme

koa-wormhole

Build Status NPM version Dependency Status

A simple, predictable, low-performance router for Koa similar to koa-router and Express 4's built-in router.

npm install --save koa-wormhole

Quickstart

const Router = require('koa-wormhole');
const app = require('koa')();

const router1 = new Router();
router1.use(function*(next) { console.log('inside router1'); yield* next; });
router1.get('/', ...);

const router2 = new Router();
router2.get('/users', ...);
router2.post('/users', ...);
router2.get('/users/:username', ...);

app.use(router1.middleware());
app.use(router2.middleware());
app.listen(3000, () => console.log('listening on 3000'));

Router middleware (middleware mounted via router.use(...)) are only run if the request matches any of the router's routes. Else, the router is skipped.

Usage

You can find a lot of examples in koa-wormhole's tests: https://github.com/danneu/koa-wormhole/blob/master/test/index.js

Basics

Just like a koa instance, a router instance has router.use(...middleware) that takes one or more middleware generator functions.

const mw1 = function*(next) {
  console.log('executing mw1');
  yield* next;
};

const mw2 = function*(next) {
  console.log('executing mw2');
  yield* next;
};

router.use(mw1, mw2);

app.use(router.middleware());

However, a router's top-level middleware will not run unless the request matches one of the router's routes.

Let's define a route for the above example.

router.use(mw1, mw2);

router.get('/test', function*() {
  console.log('executing GET /test');
  this.body = 'hello world';
});

app.use(router.middleware());

Since the two middleware were mounted before the route, they will run before the route:

$ curl http://localhost:3000/test
// executing mw1
// executing mw2
// executing GET /foo
//=> 'hello world'

And, unlike in koa-router, mount order matters in koa-wormhole.

If we mount the route before the middleware, then the middleware will not get to execute unless the route yield* next.

This is just predictable middleware behavior.

router.get('/test', function*() {
  console.log('executing GET /test');
  this.body = 'hello world';
});

router.use(mw1, mw2);

app.use(router.middleware());
$ curl http://localhost:3000/test
// executing GET /foo
//=> 'hello world'

And here's an example of what that behavior looks like when we yield next from a route handler. The request will continue down the stack, possibly matching downstream handlers.

router.get('/test', function*(next) {
  console.log('executing handler1 and yielding next');
  yield* next;
});

router.get('/test', function*(next) {
  console.log('executing handler2 and responding');
  this.body = 'ok';
});

app.use(router.middleware());
$ curl http://localhost:3000/test
// executing handler1 and yielding next
// executing handler2 and responding
//=> 'ok'

You can also mount middleware to a specific route to be run before the handler:

router.get('/test', mw1, mw2, function*() {
  console.log('executing handler');
  this.body = 'ok';
});
$ curl http://localhost:3000/test
// executing mw1
// executing mw2
// executing handler
//=> 'ok'

And it handles flattens out arrays. These are all the same:

router.get('/test', mw1, mw2, mw3, mw4, mw5);
router.get('/test', [mw1, mw2, mw3, mw4], mw5);
router.get('/test', mw1, [mw2], mw3, [mw4, mw5]);
router.get('/test', [mw1, mw2, mw3, mw4, mw5]);

URL params

koa-wormhole uses path-to-regexp to turn route paths into regular expressions, so it has the same syntax as Express4's router and koa-router.

Read its docs for more examples.

koa-wormhole exposes URL params via the this.params object.

router.get('/users/:id', function*() {
  const user = yield database.findUserById(this.params.id);
  this.assert(user, 404);

  yield this.render('show_user.html', {
    ctx: this,
    user: user
  });
});

URL param middleware

You can DRY up repetitive logic with Router#param(key, middleware).

For example, it's useful for auto-loading resources.

router.param('user_id', function*(val, next) {
  this.resource = yield db.findUserById(val);
  this.assert(this.resource, 404);
  yield* next;
});

// `this.resource` is now guaranteed to exist in these handlers
router.get('/users/:user_id', ...);
router.get('/users/:user_id/edit', ...);
router.del('/users/:user_id', ...);
router.put('/users/:user_id', ...);

// and the param middleware will not be called for these
router.get('/users', ...);
router.post('/users', ...);

Method chaining

Router#use and all of the Router#{verb}s return the router instance, so you can chain them if you'd like.

router
  .get('/users', listUsers)
  .get('/users/:id', showUser)
  .use(ensureAdmin)  // <-- only applies to downstream routes
  .del('/users/:id', deleteUser);
  .get('/users/:id/admin-panel', administrateUser);

app.use(router.middleware());

Nested Routers

3-layers deep:

const app = koa();
const r1 = new Router(), r2 = new Router(), r3 = new Router();

r3.get('/', function*(next) {
  this.body = 'hello, world!';
});
r2.use(r3.middleware());
r1.use(r2.middleware());
app.use(r1.middleware());

app.listen(3000, () => console.log('listening on 3000'));
curl http://localhost:3000
// hello, world!

General idea

I thought it'd be fun to implement a router with koa-compose, composing one long chain of generator middleware for each router.

Goals:

  • Predictable behavior
  • Simple implementation

koa-wormhole vs Express 4's built-in router

  • Express 4's routing docs: http://expressjs.com/guide/routing.html

The key difference is that koa-wormhole only pipes a request through a mounted router if the request actually matches one of the router's routes.

Consider this Express example:

// ------------------------------------------------------------
// router.js
// ------------------------------------------------------------
const router = require('express').Router();

router.use((req, res, next) {
  console.log('inside router middleware');
  next();
});

router.get('/router', (req, res, next) => {
  res.send('inside router route');
});

module.exports = router;

// ------------------------------------------------------------
// server.js
// ------------------------------------------------------------
const app = require('express')();
const router = require('./router');

app.use(router);
app.get('/', (req, res) => {
   res.send('homepage');
});
app.listen(3000, () => console.log('express listening on 3000'));

In Express, requests are always piped through routers, so top-level router middleware will always execute:

$ curl http://localhost:3000
// inside router middleware
//=> 'homepage'

$ curl http://localhost:3000/router
// inside router middleware
//=> 'inside router route'

Notice that the route's top-level middleware is always run.

Now let's look at the exact same example in koa-wormhole:

// ------------------------------------------------------------
// router.js
// ------------------------------------------------------------
const router = require('koa-wormhole')();

router.use(function*(next) {
  console.log('inside router middleware');
  yield* next;
});

router.get('/router', function*(next) {
  this.body = 'inside route route';
});

module.exports = router;

// ------------------------------------------------------------
// server.js
// ------------------------------------------------------------
const app = require('koa')();
const router = require('./router');

app.use(router.middleware());
app.get('/', function*() {
   this.body = 'homepage';
});
app.listen(3000, () => console.log('koa listening on 3000'));
$ curl http://localhost:3000
//=> 'homepage'

$ curl http://localhost:3000/router
// inside router middleware
//=> 'inside router route'

koa-wormhole skips over the router when requesting the homepage since the router had no matching route.

Why?

I just find this behavior more useful, and it's what I'm used to with koa-router.

I initially considered solutions that let you distinguish between router middleware that always runs and router middleware that only runs on router match, but I couldn't think of a use-case for that behavior.

koa-wormhole vs koa-router

koa-router has some undefined behavior and unexpected idiosyncrasies.

One example is that top-level koa-router middleware are always run before the matched handler. Consider this koa-router example:

router.get('/', function*() {
  console.log('executing router GET / handler');
  this.body = 'ok';
});

router.use(function*(next) {
  console.log('executing router middleware 1');
  yield* next;
});

router.use(function*(next) {
  console.log('executing router middleware 2');
  yield* next;
});

router.use(function*(next) {
  console.log('executing router middleware 3');
  yield* next;
});
$ curl http://localhost:3000
// executing router middleware 1
// executing router middleware 2
// executing router middleware 3
// executing router GET / handler
//=> 'ok'

I find this behavior unexpected and counter-intuitive. I'd expect the 'GET /' handler to execute first and only call the downstream middleware if it yields next, which is what koa-wormhole will do.

This may be a bug: https://github.com/alexmingoia/koa-router/issues/194

I tried to improve upon koa-router by following standard middleware intuition like declaration-order sensitivity.

License

MIT