Multiple modules in Node.js

I'm one of the organizers for NodeSchoolDC and at our meetups I see people get stuck on the exact same problem all the time. That problem is exercise 6, Make it Modular, which is the first time that you have to write a program that is split across two different files. I wanted to go ahead and explain it here to clear things up.

What does require do?

The simple version is that require is a global function set up in Node that finds another file on the system and executes it. It then gives you back whatever is the value of that file's module.exports.

/* my-module.js */
module.exports = 'hello world';

/* main.js */
var imported = require('./my-module');
console.log(imported); // hello-world

This value could be anything, including a function.

/* my-module.js */
module.exports = function(a, b) {
  return 'first: ' + a + ', second: ' + b;
};

/* main.js */
var myFunction = require('./my-module');

var result = myFunction(1, 2);
console.log(result); // first: 1, second: 2

You don't have to define the variable or function right then, it can be defined earlier.

/* my-module.js */
function myFunction(a, b) {
  return 'first: ' + a + ', second: ' + b;
}
module.exports = myFunction;

Scope inside a module

For now, you don't need to know any details about javascript closures, you just need to know that when you require a file and get back its module.exports, it has access to any variables defined in that file.

/* my-module.js */
var b = 'inside';

module.exports = function(a) {
  return 'first: ' + a + ', second: ' + b;
};

/* main.js */
var func = require('./my-module');

var result = func('outside');
console.log(result); // first: outside, second: inside

However, the file that requires that module doesn't have access to any of those variables.

/* my-module.js */
var b = 'inside';

module.exports = function(a) {
  return 'first: ' + a + ', second: ' + b;
};

/* main.js */
var func = require('./my-module');

var result = func(1);
console.log(b); // undefined

Working with callbacks

Functions are first class citizens in Javascript. That means that you can assign a function to a variable just like any other value. That includes the ones you pass into a function.

var myFunction = function(a, cb) {
  cb('first', a);
}

myFunction('second', function(myFirst, mySecond) {
  console.log('first: ', myFirst, ', second: ', mySecond)
  // => first: first, second: second
});

The code above can really easily be split up into multiple modules.

/* my-module.js */
var myFunction = function(a, cb) {
  cb('inside', a);
}

module.exports = myFunction;

/* main.js*/
var myModule = require('./my-module');

myModule('outside', function(myFirst, mySecond) {
  console.log('first: ', myFirst, ', second: ', mySecond)
  // => first: inside, second: outside
});

In Node, callbacks are always written to take an error as the first parameter. This makes it easy to handle errors inside your calling code, which makes for more maintainable programs. The above code in a more conventional style would look like this:

/* my-module.js */
var myFunction = function(a, cb) {
  cb(null, 'inside', a);
}

module.exports = myFunction;

/* main.js*/
var myModule = require('./my-module');

myModule('outside', function(null, myFirst, mySecond) {
  if (error) {
    return console.log('Error!:', error);
  }
  console.log('first: ', myFirst, ', second: ', mySecond)
  // => first: inside, second: outside
});

Multiple callbacks

The other thing that makes this exercise tricky is that you are actually dealing with two levels of callbacks.

Your main.js file needs to define a callback which your my-module.js will call after it finishes. This callback will be the last thing called, so you need to say what to do with an error if it exists and also what to do with the list of filtered files.

Your my-module will also need to define a callback for fs.readdir to call after that finishes. Inside that callback, you will need to "bubble" any errors. That means that if an error exists, you can call the callback you've received from main.js with that error. Otherwise, you can filter the files you've received and then pass null (no error) and the filtered files to the callback from main.js.

To reiterate, the key thing here is that the callback passed to my-module needs to get called inside the callback given to fs.readdir.

Conclusion

The concept of splitting code across different modules can be pretty daunting at first, but it's an incredibly useful tool for organizing your code. It helps to know that there's nothing complicated going on, just simple javascript functions. In fact, the patterns you just learned are the same ones used in all Node modules you may find in the wild, and even in the Node core itself!

I would recommend you work through this exercise on your own (you're smart, you can do it!), but afterwards if you're interested in seeing my solution, it's posted here.