Understanding module.exports and exports in Node.js
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.
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.