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

express-ko

v1.0.2

Published

Brings generators and promises to Express 4 middleware and route handlers

Downloads

10

Readme

Brings generators and promises to Express 4.x middleware and route handlers.

Build Status Coverage Status

Description

express-ko unleashes the power of coroutines on Express middleware functions with a touch of Koa (actually, it is a portmanteau of koa and co).

The package allows to use either promise-returning functions or generator functions (they are wrapped with co and may yield promises as well) as middlewares or route callbacks.

express-ko is nonintrusive. It doesn't hurt the behaviour of existing middlewares and route handlers but augments their functionality.

Differences with Koa

As opposed to Koa, this context (ctx parameter in Koa 2) is not provided for ko-ified function — particularly because Express 4.x heavily relies on function signatures, like error handlers.

Koa control flow

Koa may introduce powerful (and also confusing) patterns to the flow of control, like middleware stack bouncing:

app.use(function* (next) {
	this.state.fooBar = 'foo';
	// `next` is a generator function and can be delegated
	yield* next;
	this.body = this.state.foobar;
});

app.use(function* () {
	this.state.foobar += yield Promise.resolve('bar');
});

Express control flow

express-ko brings some syntax sugar to unsweetened callback hell, but middleware stack still works in one direction:

app.use(ko(function* (req, res, next) {
	res.locals.foobar = 'foo';
	// `next` is a function and shouldn't be delegated
	return next;
}));

app.use(ko(function* () {
	res.locals.foobar += yield Promise.resolve('bar');
	return res.locals.foobar;
}));

Usage

ko wrapper

The wrapper created with ko() converts generator functions to promises via co. It processes resolution/rejection of a promise chain from both regular and generator functions, doing nothing on the functions that don't return promises.

Patched global router (recommended)

Express router may be patched with ko.ify(express) to replace all handlers supplied to router use, param and HTTP methods with wrapper functions that look for promises and generators.

  • express can be replaced with loopback for StrongLoop LoopBack
  • ko.ify(express.Router, express.Route) can be used to supply the constructors manually
  • ko.ify(null, express.Route) will skip .use and .param patches

No manual wrapping with ko() is necessary.

let express = require('express');
let ko = require('express-ko');

ko.ify(express);

let app = express();
let router = Router();

app.use(router);
app.use(function (req, res, next) { … })
app.param(function* (req, res, next, val) { … });
router.use(function (err, req, res, next) { … });

Patched isolated router (playing safe)

A new instance of express.Router can be required with cache-mangling packages (rewire, etc) and patched.

This technique is applicable to reusable router module that shouldn't affect Express applications that host it.

Requiring entry point with rewire('express').Router may get cached Router module, it is preferable to rewire it directly.

let ko = require('express-ko');
let rewire = require('rewire');

let IsolatedRouter = rewire('express/lib/router');
let IsolatedRoute = rewire('express/lib/router/route');
 
let Router = ko.ify(IsolatedRouter, IsolatedRoute);
let router = Router();

router.use(function (err, req, res, next) { … });

module.exports = router;

Unpatched router

Each callback may be wrapped with ko() or left intact.

ko() needs extra true argument for param callbacks to distinguish them from error handlers.

let express = require('express');
let ko = require('express-ko');

let app = express();
let router = express.Router();

app.use(router);
app.use(function (req, res, next) { … });
app.param(ko(function* (req, res, next, id) { … }, true));
router.use(ko(function (err, req, res, next) { … }));

async/await

Current implementations (TypeScript, Babel, Regenerator) fall back to generator or regular promise-returning functions, so transpiled async functions can be seamlessly used with ko.

Original way

app.all('/foo', (req, res, next) => {
	….then(() => {
		res.send('foo');
		next();
	});
}); 

Suggested way (async/await)

app.all('/foo', async (req, res) => {
	await …;
	return res.send('foo');
}); 

Alternative way (generators)

app.all('/foo', function* (req, res) {
	yield …;
	return res.send('foo');
}); 

Alternative way (promises)

app.all('/foo', (req, res) => ….then(() => res.send('foo'))); 

Implicit next()

A resolution with req or res chain value executes next() and proceeds to next handler/middleware.

It is the most suitable way of treating res.send(…); next(); case.

Node.js HTTP methods (.write(), .end()) don't belong to Express API and aren't suitable for chaining; they return boolean and will cause undesirable implicit response.

Original way

app.all('/foo', (req, res, next) => {
	….then((foo) => {
		res.send(foo);
		next();
	});
}); 

Suggested way (generators)

app.all('/foo', function* (req, res) {
	let foo = yield …; 
	return res.send(foo);
}); 

Alternative way (promises)

app.all('/foo', (req, res) => ….then((foo) => res.send(foo))); 

Explicit next()

A resolution with ko.NEXT constant or next (uncalled) function values executes next() and proceeds to next handler/middleware.

It is the most suitable way of treating the handlers where no req or res are involved.

next() returns undefined, and resolving with it has no adverse effects. This behaviour of Express isn't documented and can be changed without notice.

Original way

app.all('/foo', (req, res, next) => {
	next();
}); 

Suggested way (generators)

app.all('/foo', function* () {
	return ko.NEXT;
}); 

Alternative ways (promises)

app.all('/foo', () => Promise.resolve(ko.NEXT)); 
app.all('/foo', (req, res, next) => Promise.resolve(next)); 

Explicit next('route')

A resolution with ko.NEXT_ROUTE constant value executes next('route') and proceeds to next route/middleware.

No magic word 'route' (it has got odorous code smell) has to be involved in this case.

Original way

app.all('/foo', (req, res, next) => {
	next('route');
}, …); 

Suggested way (generators)

app.all('/foo', function* () {
	return ko.NEXT_ROUTE;
}, …); 

Alternative way (promises)

app.all('/foo', () => Promise.resolve(ko.NEXT_ROUTE), …); 

Implicit next(<error>)

A resolution with Error object value causes promise chain rejection and executes next(<error>).

This behaviour is implemented to prevent the leakage of Error objects to response and shouldn't be intentionally used.

Original way

app.all('/foo', (req, res, next) => {
	next(new Error);
}); 

Suggested way

See Explicit next(<error>).

Alternative way (generators)

app.all('/foo', function* () {
	return new Error;
}); 

Alternative way (promises)

app.all('/foo', () => Promise.resolve(new Error)); 

Explicit next(<error>)

A rejection with <error> value executes next(<error>).

It is preferable to throw an object and not a string, to avoid accidental usage of magic word 'route'.

Original way

app.all('/foo', (req, res, next) => {
	next('error');
}); 

Suggested way (generators)

app.all('/foo', function* () {
	throw new Error('error')
}); 

Alternative ways (promises)

app.all('/foo', () => ….then(() => {
	throw new Error('error');
})); 
app.all('/foo', function* () {
	return Resolve.reject('error');
}); 

Implicit res.send(<response>)

A resolution with any value except undefined and number executes res.send(<response>).

It is the most suitable way of treating res.send(…) or res.json(…) with no next() case.

res.send(<number>) is deprecated in favour of res.sendStatus(<number>). See Express 4.x source code and API documentation on how res.send makes decisions on content type.

Original way

app.all('/foo', (req, res) => {
	….then((foo) => {
		res.send(foo);
	});
}); 

Suggested way (generators)

app.all('/foo', function* (req, res) {
	let foo = yield …; 
	return foo;
}); 

Alternative way (promises)

app.all('/foo', (req, res) => ….then((foo) => foo)); 

Implicit res.sendStatus(<number>)

A resolution with number value executes res.sendStatus(<number>).

It is the most suitable way of treating res.sendStatus(…) with no next() case.

Original way

app.all('/foo', (req, res) => {
	….then(() => {
		res.sendStatus(200);
	});
}); 

Suggested way (generators)

app.all('/foo', function* (req, res) {
	yield …; 
	return 200;
}); 

Alternative way (promises)

app.all('/foo', (req, res) => ….then(() => 200)); 

Examples

foobar application illustrates the new syntax for asynchronous handlers and is available in examples folder, along with foobar-vanilla application that features the original syntax for side-by-side comparison.