Modules are reusable functionality. They can be a function, a class, an object or even just simple variables.
To import a module we use the require() function in node. Require function is globally available.
NOTE: New version of Node.js supports ES6 notation of import and export statements.
Let’s see some of the features of modules in the examples below. In global.js file:
var path = require("path");console.log(`Full path: ${__filename} and filename is ${path.basename(__filename)}`);
The output of the above code, once you run the command: node global in your terminal, would be:
The path module provides us some methods to work with files/folders. The basename method returns the filename from the complete path.
__filename is a variable that is available in all node file and it refers the complete path of the current file.
How require() works
In Node, modules are referenced either by file path or by name. A module that is referenced by a name will eventually map into a file path unless the modules is a core module.
Node’s core modules expose some Node core functions to the programmer, and they are preloaded when a Node process starts (refer the IIFE above).
Other modules include 3rd party modules that you install using NPM (Node Package Manager) or local modules that you or developers around the world has created.
Each module of any type exposes a public API that the programmer can use after the module is imported into the current script.
To use a module of any type, you have to use the require function like shown below:
var module = require('module_name');
This will import a core module or a module installed by NPM. The require function returns an object that represents the JavaScript API exposed by the module.
Depending on the module, that object can be any JavaScript value, a function, an object with some properties that can be functions, an array, or any other type of valid JavaScript object.
Now, we can only import modules that have been exported.
TODO: Require flow diagram (coming up)
Exporting a Module
Node uses the CommonJS module system to share objects or functions among files.
To the end user a module exposes exactly what you specify it to.
In Node, files and modules are in 1-to-1 correspondence, which you can see in the following example.
Let’s create a file square.js, which just exports the Square constructor. For more about constructor function you can read my other story All about functions in JavaScript in 1 article
function Square(side) {function area () {return side * side;}return {area: area}}module.exports = Square;
The important bit here lies on the last line, where you define what is to be exported by the module. module is a variable that represents the module you are currently in. module.exports is the object that the module will export to other scripts that require this module.
You can export any object. In this case we are just exporting the Square constructor function, which a module user can use to create a fully functional Square instance.
Lets use this Square module in another program. Lets us create a file named square-test.js and add the following code.
// Load from current module pathvar Square = require('./square');// Since the module exports a constructor function we// have to create an instance before using it.var square = new Square(5);console.log(square.area());
The output of the above program is shown below.
One more important thing to note here is since we are importing a local module, we have to use the relative path of the module in the require statement.
Exporting multiple modules
Now let us see an example of exporting multiple objects in one module. Lets us code a small string utility module which will help us to pad strings with characters and capitalize first character of the text.
Add the below code in a file called string-module.js (you can name the file whatever you want)
function pad(text, padChar='', len) {let result = padChar.repeat(len);result = result + text + result;return result;}function capitalizeFirstLetter(text) {return text.charAt(0).toUpperCase() + text.slice(1);}module.exports.pad = pad;module.exports.capitalizeFirstLetter = capitalizeFirstLetter;
Now in the above module/file we are exporting two functions. Let us use these function in another module.
Create a file called string-module-test.js and add the below code.
var util = require('./string-module');console.log(util.pad("Hello","*",5));console.log(util.capitalizeFirstLetter("Rajesh is learning to code!"));
The output of the above code is shown below. Now, what the function actually doesn’t matter here. In our case the pad function pads a string with the padding character and length to the left and right of the original string and the capitalizeFirstLetter does what the name suggest.
NOTE: I am not doing any exception handling in the above code for brevity sake.
Now, lets us see all the three types of module loading
Loading a Core Module
Node has several modules compiled into its binary distribution. These are called the core modules and are referred by the module name, not the path.
They get preference in loading even if a third-party module exists with the same name.
For example, the following code shows how to load the core http module
var http = require('http');
This returns the http module object that supports various standard http functions. See documentation for more details.
Loading a File Module
We can also load a non-core module from the file system by providing the absolute path as shown below.
var myModule = require('/home/rajesh/my_modules/mymodule');
Or you can provide a relative path as shown below:
var myModule = require('./my_modules/module1');var myModule2 = require('./lib/module2');
NOTE: You can omit the ‘.js’ extension.
Loading a Folder Module
We can use the path for a folder to load a module as shown below:
var module1 = require('./moduleFolder');
In case we do so, Node will search inside that folder and it will presume this folder is a package and try to look for a package definition file named package.json.
If that folder does not contain a package definition file named package.json, the package entry point will assume the default value of index.js, and Node will look for a file under the path ./moduleFolder/index.js.
However, if you place a file named package.json inside the module directory, Node will try to parse that file and look for and use the main attribute as a relative path for the entry pint.
For instance, if our ./myModuleFolder/package.json file looks soke the following, Node will try to load the file with the path ./myModuleFlder/lib/module1.js:
{"name": "module1","main": "./lib/module1.js"}
Loading from the node_modules Folder
If the module name that you are loading is not relative and is not a core module, Node will try to find it inside the node_modules folder in the current directory.
For instance, in the below code snippet, Node will try to look for the file in ./node_modules/module1.js:
var module1 = require('module1.js');
If Node fails to find the file, it will look inside the parent folder called ../node_modules/module1.js.
If it fails again, it will try the parent folder and keep descending until it reaches the root or finds the required module.
We will talk about node_modules and NPM shortly.
So, here node_modules is the local folder where modules are installed/copied when you run the NPM install commands.
Caching Modules
Modules are cached the first time they are loaded, which means that every call to require(‘module1’) returns exactly the same module if the module name resolves to the exact same filename.
Let us see this in action with an example. Create a file named counter.js and a file named counter-test.js to demonstrate the caching feature.
counter.js
var counter = 1;console.log('module counter initializing...');module.exports = {value: counter};console.log('module counter initialized.');
The above module when loaded should output the text mentioned in console.log, the first time. On second require, this long should not come indicating that the file is loaded from the cache.
counter-test.js
var counter = require('./counter');console.log(counter.value);counter.value++;// Lets us require the module one more timevar counter1 = require('./counter');console.log(counter.value);
Now, you can create multiple files and load the same module and try this or within the same file, as above, the behaviour will remain same.
Now, let us take a peak at the output:
In the above code, we are requiring the module twice but you can see the initialization log only once.
You can also see that the second time we are getting the updated counter value, indicating that the module is kind of a singleton instance.