Zones for NodeJS APIs!

What are zones?

Zones are a TC39 proposal for implementing the concept of thread local storage in JavaScript. Zones allow you to scope a series of async function calls. This allows developers to create a context and invoke a series of functions and be able to get that context anywhere within that stack. Unlike if you were to create a global object and store context, you can assert the context is for that particular scope rather than the entire application’s scope.

Zones are not a new concept for programming but were first introduced to JavaScript back in 2014 when Angular2 was first being conceived. Angular uses Zones to help trigger change detection.

Why zones with Node?

Zones have a variety of use cases, one in particular we will talk about today is scoping a NodeJS request. Let’s say we want to create a unique id for every request and every time we log a statement correlate that id to the request id. That would make debugging very easy.

Since zones are still stage-0, we will need to use a polyfill named Zone.js put out by the Google team. For this demonstration, we will use Koa for http framework.

Let’s start off by creating a Koa instance, including the NodeJS version of Zone.js and scaffolding a simple middleware function.

var app = require('koa')();
require('zone.js/dist/zone-node.js');
app.use(() => {
return async (ctx, next) => {
await next()
};
});

Next, let’s update that middleware to create a new zone for each request using Zone.current.fork and passing a random id for the request context like so:

app.use((ctx, next) => {
return new Promise((resolve, reject) => {
Zone.current.fork({
name: 'api',
properties: {
id: Math.random()
}
}).run(async () => {
await next();
resolve();
});
});
});

This is great, now every request will create a new zone with the context of a unique id. Now anywhere we want to log something, we can get that id to uniquely correlate this request to any logs like so:

Zone.current.get('id');

If we want to add some automatic logging on every request, we can simply add a new piece of middleware to KOA (note: it must come after the zone middleware).

app.use(async (ctx, next) => {
const id = Zone.current.get('id');
console.log(`--> ${ctx.request.method} ${ctx.request.originalUrl} (${id})`);
await next();
console.log(`<-- ${ctx.request.method} ${ctx.request.originalUrl} ${ctx.status} (${id})`);
});

This example of logging is pretty simple but you can quickly see how it applies to a variety of use cases.

Note (5/31/17): There is currently a bug with Node’s native async/await and Zone.js where the zone will loose the context once stepped over an await.

I’m #ngPanda

I hope you enjoyed the post, if you liked it follow me on Twitter and Github for more JavaScript tips/opinions/projects/articles/etc!