Using modules is an essential part of building complete applications and software systems using Node.js. In the absence of modules, your code would be fragmented and difficult to run, let alone maintain over time. But what is a module? And how exactly are you supposed to use module.exports
to build your Node.js programs?
A module is a discrete program, contained in a single file in Node.js. Modules are therefore tied to files, with one module per file. Modules are available in other programming languages. Node.JS uses the CommonJS system of modules, but there are other module types used in the JavaScript ecosystem. The most prominent of these other module systems are the Asynchronous Module Definition (AMD) and the (ECMAScript 6) ES6 module systems.
As we will see, module.exports
is an object that the current module returns when it is "required" in another program or module.
You can include functionality from other modules in any other module. To do so is referred to as "requiring" the module, which is simply calling for a certain special object representing the functionality of the module.
Sharing Code with module.exports
For everyday use, modules allow us to compose bigger programs out of smaller pieces. Modules become the basic building blocks of the larger piece of software that collectively, they define.
Under the covers, the module keeps track of itself through an object named module
. Inside each module, therefore, 'module' refers to the object representing the current module. This object holds metadata about the module, such as the filename of the module, as well as the module's id.
Here is a little snippet of code you can run to see the values of these example properties on a module:
// module1.js
console.log(module.filename);
console.log(module.id);
console.log(module.exports);
You can run this using the command node module1.js
. You will see, for example, that the module.filename
property is set to a file path that ends with the correct name of the file in which this module exists, which is module1.js
. Here is an example output for the code above:
$ node module1.js
/Users/scott/projects/sandbox/javascript/module-test/module1.js
.
{}
In Node.js, the practice of making a module's code available for other modules to use is called "exporting" values.
But if a piece of complex software is to be built from individual modules, you may already be thinking about the next questions:
Important Question #1: How does Node.js identify the "main" module to start running the program?
Node.js identifies the main module to run by the arguments that get passed to the node
executable. For instance if we have a module contained in the file server.js
, along with other parts of our program contained in files login.js
and music_stream.js
, invoking the command node server.js
identifies the server module as the main one. That main module, in turn, will call for the functionality in the other modules by "requiring" them.
Important Question #2: How does a module share its code with other modules?
The module
object has a special property, called exports
, which is responsible for defining what a module makes available for other modules to use. In Node.js terminology, module.exports
defines the values that the module exports. Remember that "exporting" is simply making objects or values available for other modules to import and use.
Therefore, we can export any value or function or other object we would like to export by attaching it as a property of the module.exports
object. For example, if we would like to export a variable named temperature
, we could make it available for use outside the module by simply adding it as a new property of module.exports
as follows:
module.exports.temperature = temperature;
Exporting and Requiring Functions and Variables with module.exports
Now that we have seen the conceptual meaning of a module, as well as why we use modules, let's put into practice these ideas by actually creating a module, defining functions, and then exporting those functions so that they can be used by other modules.
As an example, here is a new module that gives book recommendations. In the code below, I have defined a variable as well as a few functions. Later, we will access the functionality of these book recommendations from another module.
// book_recommendations.js
// stores the favorite author in a constant variable
const favoriteAuthor = { name: "Ken Bruen", genre: "Noir", nationality: "Irish" };
// returns the favorite book
function favoriteBook() {
return { title: "The Guards", author: "Ken Bruen" };
}
// returns a list of good books
function getBookRecommendations() {
return [
{id: 1, title: "The Guards", author: "Ken Bruen"},
{id: 2, title: "The Stand", author: "Steven King"},
{id: 3, title: "The Postman Always Rings Twice", author: "James M. Cain"}
];
}
// exports the variables and functions above so that other modules can use them
module.exports.favoriteAuthor = favoriteAuthor;
module.exports.favoriteBook = favoriteBook;
module.exports.getBookRecommendations = getBookRecommendations;
We've added all the variables and functions we would like to export to module.exports
as properties of the object. We have just accomplished our goal of exporting these functions and variables from the book_recommendations
module.
Now let's see how we would be able to import this module and access its functionality from another module.
In order to import the module, we need to use a special keyword used to import things, and it is called require. Where module.exports
lets us set things for export, require
lets us specify modules to be imported into the current module.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
The functionality for importing modules is provided in a module named require
, available on the global scope. This module's main export is a function to which we pass the path of the module we would like to import. For instance, to import a module defined in music.js
, we would require('./music')
, where we have specified the relative path.
Now we can see how easy it is to import anything using require
. Going back to our book_recommendations
module, we can import it and access the functions it exports. This is shown in the next code listing. This module prints a message describing recommended birthday gifts. It gets recommended books from the imported book recommendations module, and combines them with music recommendations.
Create a new module as shown below, then run it as shown earlier to see it using the functions defined in the imported book_recommendations
module.
// birthday_gifts.js
// import the book recommendations module
let books = require('./book_recommendations');
// gets some music recommendations as well
let musicAlbums = [
{ artist: "The Killers", title: "Live From The Royal Albert Hall" },
{ artist: "Eminem", title: "The Marshall Mathers LP" }
];
// the two best items from each category
let topIdeas = function() {
return [musicAlbums[0], books.favoriteBook()];
}
// outputs a message specifying the customer's recommended gifting items
let gifts = function() {
console.log("Your recommended gifts are:\n");
console.log("######MUSIC######");
for (let i = 0, len = musicAlbums.length; i < len; i++) {
console.log(musicAlbums[i].title + " by " + musicAlbums[i].artist);
}
console.log("######BOOKS######");
let recommendedBooks = books.getBookRecommendations();
for (let i = 0, len = recommendedBooks.length; i < len; i++) {
console.log(recommendedBooks[i].title + " by " + recommendedBooks[i].author);
}
console.log("\n\nYours");
console.log("Shop Staff\n*************");
console.log("P.S. If you have a limited budget, you should just get the music album " + topIdeas()[0].title + " and the book " + topIdeas()[1].title + ".");
}
console.log("Welcome to our gift shop.\n");
// Get the gifts
gifts();
As you can see, we used require
to import the book_recommendations
module. Inside the new module, we could access variables and functions that had been exported by adding them to module.exports
.
With both modules complete, invoking node birthday_gifts.js
prints out a neat message with the customer's complete gift recommendations. You can see the output in the following image.
Welcome to our gift shop.
Your recommended gifts are:
######MUSIC######
Live From The Royal Albert Hall by The Killers
The Marshall Mathers LP by Eminem
######BOOKS######
The Guards by Ken Bruen
The Stand by Steven King
The Postman Always Rings Twice by James M. Cain
Yours
Shop Staff
*************
P.S. If you have a limited budget, you should just get the music album Live From The Royal Albert Hall and the book The Guards.
This pattern of composing Node.js programs from smaller modules is something you will often see, like with Express middleware, for example.
Exporting And Requiring Classes with module.exports
In addition to functions and variables, we can also use module.exports
to export other complex objects, such as classes. If you are not familiar with using classes or other Node.js fundamentals, you can take a look at our Node.js for beginners guide.
In the following example, we create a Cat class which contains a name and age for Cat objects. Then we export the Cat class by attaching it as a property of the module.exports
object. As you can see, this is not that different from how we exported functions and variables before.
// cat.js
// constructor function for the Cat class
function Cat(name) {
this.age = 0;
this.name = name;
}
// now we export the class, so other modules can create Cat objects
module.exports = {
Cat: Cat
}
Now we can access this Cat class by importing the cat
module. Once that's done, we can create new Cat objects and use them in the importing module as shown in the following example. Again, you should try running this code with node cat_school.js
to see the names and ages of the new cats at your command prompt.
// cat_school.js
// import the cat module
let cats = require('./cat');
let Cat = cats.Cat;
// creates some cats
let cat1 = new Cat("Manny");
let cat2 = new Cat("Lizzie");
// Let's find out the names and ages of cats in the class
console.log("There are two cats in the class, " + cat1.name + " and " + cat2.name + ".");
console.log("Manny is " + cat1.age + " years old " + " and Lizzie is " + cat2.age + " years old.");
As we just saw, exporting a class can be accomplished by attaching the class as a property of the module.exports
object. First, we created a class using a constructor function. Then we exported the class using module.exports
. To use the class, we then required it in another module, and then created instances of the class.
For an example exporting a class that was created with ES6 syntax, see the Book
class below.
An Alternative: Using the Shorthand exports VS module.exports
While we can continue assigning things for export as properties of module.exports
, there exists a shorthand way of exporting things from the module. This shorthand way involves using just exports
instead of module.exports
. There are some differences between the two. The key thing to notice here, however, is that you must assign your new values as properties of the shortcut export
object, and not assign objects directly to overwrite the value of export
itself.
Here is an example in which I use this shorthand way to export a couple of objects from a module named film_school
.
// film_school.js
// a beginner film course
let film101 = {
professor: 'Mr Caruthers',
numberOfStudents: 20,
level: 'easy'
}
// an expert film course
let film102 = {
professor: 'Mrs Duguid',
numberOfStudents: 8,
level: 'challenging'
}
// export the courses so other modules can use them
exports.film101 = film101;
exports.film102 = film102;
Notice how we are assigning the objects as, for example, exports.film101 = ...
instead of exports = film101
. That later assignment would not export the variable, but mess up your shortcut exports entirely.
The exporting done in the shorthand way above, could have been achieved in the long-form way we have used with module.exports
using the following lines for the exporting.
// export the courses so other modules can use them
module.exports.film101 = film101;
module.exports.film102 = film102;
We could also export the two objects by assigning an object directly to module.exports
but this would not work with exports
.
// export the courses so other modules can use them
module.exports = {
film101: film101,
film102: film102
}
The two are very similar, and rightly so. These are two ways of achieving the same thing, but exports
can trip you up if you assign an object to exports the way you would assign to module.exports
.
Differences Between Node.js Modules and ES6 Modules
The modules used in Node.js follow a module specification known as the CommonJS specification. The recent updates to the JavaScript programming language, in the form of ES6, specify changes to the language, adding things like new class syntax and a module system. This module system is different from Node.js modules. A module in ES6 looks like the following:
// book.js
const favoriteBook = {
title: "The Guards",
author: "Ken Bruen"
}
// a Book class using ES6 class syntax
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
describeBook() {
let description = this.title + " by " + this.author + ".";
return description;
}
}
// exporting looks different from Node.js but is almost as simple
export {favoriteBook, Book};
To import this module, we'd use the ES6 import
functionality, as follows.
// library.js
// import the book module
import {favoriteBook, Book} from 'book';
// create some books and get their descriptions
let booksILike = [
new Book("Under The Dome", "Steven King"),
new Book("Julius Ceasar", "William Shakespeare")
];
console.log("My favorite book is " + favoriteBook + ".");
console.log("I also like " + booksILike[0].describeBook() + " and " + booksILike[1].describeBook());
ES6 modules look almost as simple as the modules we have used in Node.js, but they are incompatible with Node.js modules. This has to do with the way modules are loaded differently between the two formats. If you use a compiler like Babel, you can mix and match module formats. If you intend to code on the server alone with Node.js, however, you can stick to the module format for Node.js which we covered earlier.
Conclusion
The use of module.exports
allows us to export values, objects and styles from Node.js modules. Coupled with the use of require
to import other modules, we have a complete ecosystem for composing large programs out of smaller parts. When we combine a number of modules that take care of unique parts of functionality, we can create larger, more useful, but easy to maintain applications and software systems.