Node Module Design

Maurice Butler / @butlermaurice

How does require handel files

When you call require and pass a value, node makes a desicion based on the format of that string.

The process it uses is written in sudo code in the documentation.

module.exports

When you create a module you (usualy) want to be able to export something to be used by the calling code.

To do this the result of a require call will always be the value of the module.exports property.



// test.js
module.exports = 'myCoolTestValue';
					

// index.js
var testValue = requie('./test');

console.log(testValue === 'myCoolTestValue'); // true
					

A reference to module.exports is also available as exports.

Imagine this line of code is inserted at the top of every module.



var exports = module.exports = {};
					
This means than exports can be used as a shorter reference to module.exports, but I prefer not to uses it to avoid confusion when that reference is broken.

Exporting a namespace

The most common use case for a module is to return a namespace or object that groups related functionality together.

This is done by setting module.exports to an object that has properties pointing to the functionality


// simpleMath.js

function add(a, b){
    return a + b;
}

function subtract(a, b){
    return a - b;
}

module.exports = {
    add: add,
    subtract: subtract
};
					

var simpleMath = require('./simpleMath');

simpleMath.add(2, 3); // 5
					

Exporting a function / constructor

Another pattern is to return a single function or constructor.

This is done by setting module.exports the function and is an example of why not to use exports.


// dog.js

function Dog(){
    this.name = 'Rover';

    this.speak = function(){
    	console.log('Woof');
	}
}

// This will not work
// exports = Dog;

module.exports = Dog;
					

var Dog = require('./dog'),
	rover = new Dog();

rover.speak(); // Woof
					

Exporting a higher order function

A higher-order function is a function that returns another function.

This is usefull if you want to return a function from your module but need additional input to do so.


// fooLogger.js

function createFooLogger(logger){

    return function(value){
    	logger.log('foo ' + value)
	}
}

module.exports = createFooLogger;
					

var fooLogger = require('./fooLogger')(console);

fooLogger('bar'); // foo bar
					

Exporting a global singleton

Because require caches the value assigned to module.exports based on the resolved filename, all subsequent calls to require() will (usualy) return the same instance.


There is a case where sub modules may have their own versions of a module in their node modules folder and in this case the require call will resolve to a different filename and thus return a different instance of the module.

Exporting a global singleton

This is not very common and probably points to a bigger problem with dependencies.

As a result, this is not recomended for every day, use but it can be resolved.


// singleton.js - module 1

if(global.myCoolSingleton){
	module.exports = global.myCoolSingleton;
	return;
}

var myCoolSingleton = 'Version 1';

module.exports = global.myCoolSingleton = myCoolSingleton;
					

// singleton.js - module 2

if(global.myCoolSingleton){
	module.exports = global.myCoolSingleton;
	return;
}

var myCoolSingleton = 'Version 2';

module.exports = global.myCoolSingleton = myCoolSingleton;
					

Exporting a global singleton




var singleton1 = require('./singleton'), // module 1
	singleton2 = require('some/other/module/singleton'); // module 2

console.log(singleton1) // 'Version 1'
console.log(singleton2) // 'Version 1'
					

Single run or extention code

Modules do not nessesarily have to return something to be usefull.

Because the code is run at require, they can also be used to run single use code or to extend other objects.


// stringExtentions.js
String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g, '');
};

					

var foo = ' My cool string   ';

console.log(foo.trim); // undefined

require('./stringExtentions');

console.log(foo.trim); // [Function]

console.log(foo.trim()); // 'My cool string'
					

Real World Application

Lets build a modular http server that:


  • Loads config from a JSON file
  • Passes all requests to a router
  • Uses an external module to instanciate objects
  • Uses another external module to perform shared logic

Node Module Design

Maurice Butler / @butlermaurice