JavaScript - - By Cho S. Kim

Understanding module.exports and exports in Node.js

For a high-quality, in-depth introduction to Node.js, you can’t go past Canadian full-stack developer Wes Bos. Try his course here, and use the code SITEPOINT to get 25% off and to help support SitePoint.

As developers, we often face situations where we need to use unfamiliar code. A question will arise during these moments. How much time should I invest in understanding the code that I’m about to use? A typical answer is learn enough to start coding; then explore that topic further when time permits. Well, the time has come to gain a better understanding of module.exports and exports in Node.js. Here’s what I have learned.

Note: This post covers using modules in Node. If you want to learn how you can use modules inside of the browser, read: Understanding JavaScript Modules: Bundling & Transpiling

What is a Module

A module encapsulates related code into a single unit of code. When creating a module, this can be interpreted as moving all related functions into a file. Let’s illustrate this point with an example involving an application built with Node.js. Imagine that we created a file called greetings.js and it contains the following two functions:

// greetings.js
sayHelloInEnglish = function() {
  return "Hello";
};

sayHelloInSpanish = function() { return "Hola"; };

Exporting a Module

The utility of greetings.js increases when its encapsulated code can be utilized in other files. So let’s refactor greetings.js to achieve this goal. To comprehend what is actually happening, we can follow a three-step process:

1) Imagine that this line of code exists as the first line of code in greetings.js:

// greetings.js
var exports = module.exports = {};

2) Assign any expression in greetings.js that we want to become available in other files to the exports object:

// greetings.js
// var exports = module.exports = {};

exports.sayHelloInEnglish = function() { return "HELLO"; };

exports.sayHelloInSpanish = function() { return "Hola"; };

In the code above, we could have replaced exports with module.exports and achieved the same result. If this seems confusing, remember that exports and module.exports reference the same object.

3) This is the current value of module.exports:

module.exports = {
  sayHelloInEnglish: function() {
    return "HELLO";
  },

sayHelloInSpanish: function() { return "Hola"; } };

Importing a Module

Let’s import the publicly available methods of greetings.js to a new file called main.js. This process can be described in three steps:

1) The keyword require is used in Node.js to import modules. Imagine that this is how require is defined:

var require = function(path) {

// ...

return module.exports; };

2) Let’s require greetings.js in main.js:

// main.js
var greetings = require("./greetings.js");

The above code is equivalent to this:

// main.js
var greetings = {
  sayHelloInEnglish: function() {
    return "HELLO";
  },

sayHelloInSpanish: function() { return "Hola"; } };

3) We can now access the publicly available methods of greetings.js as a property of our greetings variable in main.js.

// main.js
var greetings = require("./greetings.js");

// "Hello" greetings.sayHelloInEnglish();

// "Hola"
greetings.sayHelloInSpanish();

Important Points

The keyword require returns an object, which references the value of module.exports for a given file. If a developer unintentionally or intentionally re-assigns module.exports to a different object or different data structure, then any properties added to the original module.exports object will be unaccessible.

An example will help elaborate this point:

// greetings.js
// var exports = module.exports = {};

exports.sayHelloInEnglish = function() { return "HELLO"; };

exports.sayHelloInSpanish = function() { return "Hola"; };

/*

  • this line of code re-assigns
  • module.exports */ module.exports = "Bonjour";

Now let’s require greetings.js in main.js:

// main.js
var greetings = require("./greetings.js");

At this moment, nothing is different than before. We assign the variable greetings to any code that is publicly available in greetings.js.

The consequence of re-assigning module.exports to a data structure other than its default value is revealed when we attempt to invoke sayHelloInEnglish and sayHelloInSpanish:

// main.js
// var greetings = require("./greetings.js");

/*

  • TypeError: object Bonjour has no
  • method 'sayHelloInEnglish' */ greetings.sayHelloInEnglish();

/*

  • TypeError: object Bonjour has no
  • method 'sayHelloInSpanish' */ greetings.sayHelloInSpanish();

To understand why these errors are occuring, let’s log the value of greetings to a console:

// "Bonjour"
console.log(greetings);

At this point, we are trying to access the methods sayHelloInEnglish and sayHelloInSpanish on the string “Bonjour.” module.exports, in other words, is no longer referencing the default object that contain those methods.

Conclusion

Importing and exporting modules is a ubiqutous task in Node.js. I hope that the difference between exports and module.exports is clearer. Moreover, if you ever encounter an error in accessing publicly available methods in the future, then I hope that you have a better understanding of why those errors may occur.

Recommended Resources

Wes Bos
Learn to build applications and APIs with Node.js. Use coupon code 'SITEPOINT' at checkout to get 25% off.

There are many ways to start a local Node server. Watch as this screencast explores some of the common node server techniques and how to instantiate them.