Developing framework agnostic Node.JS middleware

As a Node.JS HTTP server developer, you may have encountered the annoying situation of having to learn an entirely new ecosystem of re-usable server modules, also known as middleware or plugins, as you move across projects. For the purpose of this article we will refer to these re-usable server modules as middleware.

You may have worked on projects using connect, express, restify, hapi, or koa. If you’ve come to lean on a certain middleware in express, it makes sense that one would seek out similar functionality when trying out hapi for example.

Fortunately and unfortunately, it’s generally pretty easy to find a specific middleware duplicated across frameworks.

I am not satisfied with the duplication of algorithms under the guise of supporting multiple libraries/frameworks and neither should you.

We do not need 10–20+ duplicate packages that do the same thing solely for the purpose of supporting a different framework such as connect, express, restify, hapi, or koa.

While the status quo has been that middleware developers duplicate packages to support a alternative framework; it doesn’t have to be this way. Unfortunately, practices will not change until developers understand the issue.

The issue at hand is that HTTP server frameworks do not expose a uniform request/response API.

This issue is not unique to Node.JS. There are tons of articles floating around the internet about how UI plugins should not be targeting libraries like jQuery specifically. That being said, this piece is about Node.JS and HTTP servers, not UI plugins and jQuery.

If middleware developers were to leverage the intrinsic NodeJS http.Server API instead of targeting framework specific APIs we’d see less duplication.

I personally do not care for articles that bash a prevailing technique yet do not offer alternative solutions. As such, below are two example modules for identifying requests with connect/express and koa respectively.

The idea is that if a request comes in with a header of X-Request-Id, we use the value given as the ID for the request/response lifecycle. If no X-Request-Id header is provided, we generate a unique identifier. The ID should be accessible throughout the application.

// express || connect
app.use(function rid (req, res, next) {
res.id = req.headers['x-request-id'] || uuid.v4()
res.setHeader(‘X-Request-Id’, req.id)
next()
})

or

// koa
app.use(function * rid (next) {
this.request.id = this.get('X-Request-Id') || uuid.v4()
yield next
this.set('X-Request-Id', this.request.id)
})

Besides the app.use() function, the two middleware are incompatible as written above despite exposing the same functionality.

Koa middleware are generator functions which expose request and response objects referenced via this; however, those request and response objects expose an API that deviates from the intrinsic Node.JS http.Server request and response API.

Connect and express middleware functions accept a request (req) and response (res) object as function parameters. These are actually the intrinsic Node.JS request and response objects.

What we’ve just learned is that if we do a little bit of planning, we can integrate with multiple frameworks without over coupling our functionality to a specific framework.

We can delegate to the Node.JS request and response API instead. Consider the following alternative middleware.

'use strict'
const uuid = require('uuid').v4
module.exports = requestId
function requestId (req) {
const id = req.headers['x-request-id'] || uuid()
  return {
setHeader (res) {
res.setHeader('x-request-id', id)
return this
},
    getId () {
return id
}
}
}

Keep in mind that the above can be so much more customizable. For example, we could allow the user to pass in the name of the header or an alternative random ID generator. I kept the example simple for purposes of illustration. Further, the fluent API style is not a necessity, it is simply the one I chose for this example.

// express || connect (via res and req)
app.use(function (req, res, next) {
req.id = requestId(req).setHeader(res).getId()
next()
})

or

// koa (via this.res and this.req)
app.use(function * (next) {
this.request.id = requestId(this.req).setHeader(this.res).getId()
yield next
})

This slightly changes how the user integrates the middleware. The prevailing strategy has been to expose the middleware as a function, perhaps supporting options passed in as an object.

For example (using express):

var requestId = require('request-id');
var app = require('express')();
app.use(requestId());

In order to integrate our updated middleware, we provide an anonymous function wherein we explicitly call the requestId function passing in the request and response objects as depicted below:

var requestId = require('request-id');
var app = require('express')();
// You may optionally extract the following function to a module.
app.use(function (req, res, next) {
req.id = requestId(req).setHeader(res).getId();
next()
})

This is much more explicit which allows for more flexibility. I realize this is a departure from the way a most middleware are designed and used; however, I believe that if middleware developers adopt this strategy, the ecosystem will be better for it.

If you have any thoughts, suggestions, questions or feedback, just hit me up via twitter @wilmoore, npm @wilmoore, or github @wilmoore.

Show your support

Clapping shows how much you appreciated Wil Moore III’s story.