There's a common design pattern in the Node.js community that has been bothering me for some time since it results in less readable code. The issue relates to designing modules that export a single factory function. Let's take a look at some example code using the popular express module:
var express = require('express');
var app = express();
The above code first requires the express
module to get a reference to a function that, when invoked, will create a new Express app instance.
The following code has equivalent results, but it has been reduced to a concise one-liner, albeit less readable:
var app = require('express')();
The implementation for this pattern will be similar to the following:
function Foo(options) {
// ...
}
Foo.prototype = {
// ...
}
module.exports = function(options) {
return new Foo(options);
}
We can do better
Instead of having the express
module export a factory function, why not have the module export an object with a factory method? For example, if we update the express
module to export a createApp()
method then the usage becomes much cleaner (even when reduced to a one-liner):
var app = require('express').createApp();
With this small change, we only need to look at the right-hand side (i.e. require('express').createApp()
) to understand the usage of the express
module and the name of the variable on the left-hand side does not matter. Also, the one-liner is more readable since it avoids the adjacent parenthesis blocks.
The updated implementation of this pattern will be similar to the following:
function Foo(options) {
// ...
}
Foo.prototype = {
// ...
}
exports.createFoo = function(options) {
return new Foo(options);
}
Exporting a constructor function
It is also common to find a module that exports a constructor function and this also has issues. This time we will pick on the cookies module which has usage similar to the following:
var Cookies = require('cookies');
var cookieJar = new Cookies(req, res);
Notice the awkward changing of case when requiring the module? The typical JavaScript style guide will suggest that variables referencing constructor functions should use upper camel case and this is at odds with npm that requires that the names of all published modules be lower case (thus, not allowing: var Cookies = require('Cookies')
). Again, I think we can do better. Instead of the module exporting a constructor function, we could again switch to using a factory method:
var cookies = require('cookies');
var cookieJar = cookies.createCookieJar(req, res);
A side benefit is that the user does not need to use the new
keyword.
In addition, we could also choose to export the constructor function as part of the exports to allow for the following:
var CookieJar = require('cookies').CookieJar;
var cookieJar = new CookieJar(req, res);
You would typically not want to export the constructor function unless you want to allow for external prototypal inheritance on the exported type or to allow the prototype of the exported type to be monkey-patched by external modules. Whether or not it makes sense to publicly export the various constructor functions that are used internally will vary by module.
Conclusion
When building tiny and focused Node.js modules, don't forget to make sure that you expose an API that results in readable code. Sometimes small changes can introduce more clarity even if it requires that the user type out a few more characters. As a module author, I suggest avoiding module.exports
to export a single factory function and, instead, opt for exporting factory methods.