Module – Share Code & Instances

Module is an important concept in Nodejs to organize the code to create re-useable assets as well as share the instances.

fs – Module for working on the files & directories,
http – Module for creating http server, working on request and response objects,
events – Module for creating and consuming events using event emitters ;

are some examples from core Nodejs modules which groups the constants, functions, classes ect. related to their respective features.

Such groupings not only help us create re-usable assets but, also, help us create shared instances, as the Nodejs caches the exported instances `by default on loading.

 

1 How to create and use a local module ?

 

1. a Export from a local module

As mentioned above a module may contain constants, variables, function or class definitions. In order to make them available to another module, we can export them as a value assigned to module.exports as shown below.

function sayHello(name){
  console.log(`Hello : ${name.toUpperCase()}`);
}

class Duck {
  sound(){
    console.log('quack...quack...quack...');
  }
}

//The default value of modlue.exports is an empty JSON object --> module.exports = {};
module.exports.x = 'x';              //exports a variable
module.exports.sayHello = sayHello;  //exports a function
module.exports.duck = new Duck();    //exports an instance
module.exports.Duck = Duck;          //exports a Class
1.b Import from a local module

To use them from a different file, we can import this local module like any other module using ‘require‘.

The require() takes the relative file location and assigns the value of module.exports as shown below.

//require() assigns the value of module.exports
const myModule =require('./my-module.js');

myModule.sayHello("David");   //prints - Hello : DAVID
myModule.duck.sound();        //prints - quack...quack...quack...

duck = new myModule.Duck();
duck.sound();                 //prints - quack...quack...quack...

In case we want to get the exported components separately, we can do that as shown below:

const {sayHello, duck, Duck} = require('./my-module.js');

sayHello("David");    //prints - Hello : DAVID
duck.sound();         //prints - quack...quack...quack...
new Duck().sound();   //prints - quack...quack...quack...

 

2. Using ‘exports’ – An alias for shortcut

While exporting the components, we often use ‘exports’ instead of ‘module.exports’ as a shortcut. Importantly, the nodejs initializes ‘exports’ as an alias to ‘module.exports’ internally before our code executes.

//What a module does internally before our code executes...
var module.exports = {};
var exports = module.exports; //Both refer to the same empty JSON object

Since, nodejs exports only the values assigned to module.exports :

  • We should not to assign any new object to ‘exports’, because it will remove the reference to ‘module.exports’
  • Rather we should only assign new attributes through the ‘exports’ alias as shown below.
function sayHello(name){
  console.log(`Hello : ${name.toUpperCase()}`);
}


//Initially both module.exports and exports refer to the same empty JSON object
//Adding new attributes to exports is fine
exports.sayHello = sayHello;
exports.x = 'x';
exports.y = 'y';

//Updating an existing attribute on exports is also fine
exports.x ='xx';   //This will work as it is updating module.exports.x

//Assigning a new object to exports is a mistake
exports   = {x : 'xxx'}; //Now exports is no more the same module.exports
//All the following assignments will not be exported
exports.y = 'yy';

Thus, for reference, the output of the following code will be as specified in the comments :

const myModule=require('./exports-alias.js');

myModule.sayHello("David");  //prints- Hello : DAVID

console.log('x = '+ myModule.x); //prints - xx
console.log('y = '+ myModule.y); //prints - y

 

3. Caching on load helps us share the instances

function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}
exports.a =  getRandomInt(10);
const a1 = require('./module.js');
const a2 = require('./module.js');

//getRandomInt(10) executes only once 
console.log(a1.a == a2.a); //prints -true

a1.a = 50; 
//Both refer to the same variable
console.log(a1.a == a2.a); //prints -true

Besides allowing us to re-use the code, the modules also help us share the instances. It’s because when we load any module using require(), nodejs caches the exported components. Hence, on the subsequent loads we get the same instances. Thus, it keeps the application lighter with shared instances.

 

4. Exporting from ECMAScript modules

In case we are using *.mjs files using ECMAScripts, we need to use import and export statements instead of require.

We can not import a *.mjs module on a *.js file but, we can import from a *.js file into *.mjs file.

Export Module
 function sayHello(name){
  console.log(`Hello : ${name.toUpperCase()}`);
}

class Duck {
  sound(){
    console.log('quack...quack...quack...');
  }
}

var duck = new Duck();

export {sayHello, duck, Duck};
function sum(a,b){
  return a+b;
}
exports.sum = sum;
Import Module
import { sayHello, duck, Duck } from './my-ecma-module.mjs';
import cjsModule from './my-commonjs-module.js';

sayHello("David");    //prints - Hello : DAVID
duck.sound();         //prints - quack...quack...quack...
new Duck().sound();   //prints - quack...quack...quack...

console.log(cjsModule.sum(20,40)); //prints - 60