module.exports and ES modules (ESM) import export in Node.js

Cover Image for module.exports and ES modules (ESM) import export in Node.js
Ali Bentaleb

The module.exports is an object created by the Module system on Node.js. Modules are made in JavaScript to group code together so it can be shared and reused later by other modules. In Node.js the adopted module system is called CommonJS or shortly CJS and where each file is considered as a module, however all variables or objects are private and not exposed to the outer world unless they are assigned to module.exports or exports.

Modules in JavaScript

Several entities have defined ways of making modules, among these definitions:

  • CJS (Common JS): is used by Node.js to define modules and is based on exports and require syntax.
  • ESM (ECMAScript Module): is the standard Module for JavaScript defined by ECMA where we can use import and export keywords. The Node.js also supports this syntax lately without use of transpilers like Babel to convert the ESM to CJS for Node.js applications like the case for React applications.

In this article we will cover both CJS and ESM exports.

module.exports in CommonJS modules

The module.exports is an object provided by Node.js and it serves to export objects or functions from a file and make it available on another file using the require keyword, let’s take a look to learn more:

To Apply create a file called exportSample.js, and copy the content below:

//the module.exports is an object where we can assign objects or functions
//in our case, we have assigned two functions, a class and a value to be used elsewhere
module.exports = {
	Avatar: class Avatar {
		constructor(name, hobby) {
			this.name = name;
			this.hobby = hobby;
		}
		getAvatar = () => {
			return `My name is ${this.name} and my hobby is ${this.hobby}`;
		};
	},
	sayHello: (x) => console.log('hello ' + x),
	sayBye: (x) => console.log('bye ' + x),
	myValue: 2
};

What we have made is export two functions, an avatar class and a value using module.exports, so we are assigning these two functions, the class and the value to our module.exports which will made them visible to other files, or in Node.js terms, modules.

Let's see how we can call them.

You can create another file in the same directory with importSample.js and copy this:

//we call the exposed object by using require provided by Node.js and named it me for example
const me = require('./exportSample');

//we call the function sayHello contained in the objects me
me.sayHello('Ali');

//we use the object Avatar which is a class named Avatar also and contains a method
// the class is instancied using some attributes and the getAvatar method is invoked then
const avatar = new me.Avatar('Ali', 'Computer science');

console.log(avatar.getAvatar());

me.sayBye('Ali');

console.log(me.myValue);

/* Results are: 

hello Ali
My name is Ali and my hobby is Computer science
bye Ali
2
*/

exports in CommonJS modules

It is also possible to use _exports_ keyword to make public objects in modules to be available for other modules, and it points to the same object _module.exports_, let's take a look at this sample and understand the main difference between both keywords.

The syntax would change a little bit as we cannot assign object and access its elements as we would do directly with module.exports, instead we should assign every bit to the exports and use it.

Comment the previous content, and add the below to your exportSample.js file:

exports.Avatar = class Avatar {
	constructor(name, hobby) {
		this.name = name;
		this.hobby = hobby;
	}
	getAvatar = () => {
		return `My name is ${this.name} and my hobby is ${this.hobby}`;
	};
};

exports.sayHello = (x) => console.log('hello ' + x);

exports.sayBye = (x) => console.log('bye ' + x);
exports.myValue = 2;
Now you can use the same file _importSample.js_ defined earlier, run it using
> node importSample.js
And it should return the same results as when using _module.exports_ earlier.

Differences between module.exports and exports

So what is the difference between module.exports and exports?

First difference

  • module.exports exposes the object it points to, and in our sample above, we have seen that it is possible to assign a big object containing a class , methods and variables in one shot and get it by using require
  • exports exposes the properties of the object it points to, so we assign properties one by one and get them again by using require also.

Second difference

  • exports points to module.exports and they are both objects, however what is being exported is what have been assigned to module.exports, so in case both keywords exist in the same file, no matter what order the module.exports came in, its assigned object will be the only one visible to outside modules. A sample below would clear things out:
// exports with module.exports
module.exports = {
	saySomething: (x) => console.log('Something ' + x)
};

exports.sayHello = (x) => console.log('hello ' + x);
exports.sayBye = (x) => console.log(' bye ' + x);
exports.rr = 2;

We define both keywords in the same file, each one is exporting something different and we will try to call out exported objects and see what is defined.

const me = require('./exportSample');

me.saySomething('bla bla');
//Something bla bla

me.sayHello('Ali');
//TypeError: me.sayHello is not a function

As we can see, even if the exports came last in file order nothing was exposed from exports assignments, only the module.exports assignment is exposed.

import export modules in Node.js

Node.js supports ESM modules or import export syntax since v12.22.7 as a stable version, you can use any higher version than that to use ESM modules, for example the V14.x LTS (Long Term Support) to work with.

Check first which version you have in your Node.js

node -v
//v14.17.6

To enable ESM you either has to:

  • rename your file with .mjs extension.
  • add “type”:“module” in your package.json.
  • use the flag –input-type with node argument and read from stdin.

import export syntax

in ESM, export serves to export functions or objects from a module, and it has two types of export:

  • Named exports: to export multiple objects or functions from one module.
  • Default export: export the default export per module and it is limited to 1.

Named exports

Named exports are used to export several functions or objects, and to import them you need to keep the same given name when they were exported.

To test, comment previous code and copy this to exportSample.js

//export a function
export const f = (x) => x * 2;

export const sayHello = (x) => console.log('hello ' + x);
//export a class
export class Avatar {
	constructor(name, hobby) {
		this.name = name;
		this.hobby = hobby;
	}
	getAvatar = () => {
		return `My name is ${this.name} and my hobby is ${this.hobby}`;
	};
}
Comment previous code in _importSample.js_, make sure you have applied _**"type":"module"**_ in the _package.json_ , so in your folder you need to have 2 javaScript files and the _package.json_ file and then run the script with _node importSample.js_.
//note that we have kept the same name f and rename it as multiply 2
// also with named exports you need to have curly braces even
// if you import one object from file
import {f as multiply2, Avatar} from './exportSample.js';

const x = 2;

console.log(multiply2(2));
//4

const avatar = new Avatar('Ali', 'Computer science');
console.log(avatar.getAvatar());
//My name is Ali and my hobby is Computer science

Note that Named exports cannot be renamed, in our case when importing f, we kept the same name and transform it with as mutlitply2; also make sure to put curly braces with named exports even if you import one object.

Default exports

A default export is used to export a single object and its syntax is different than named exports

const sayHello = (x) => console.log('hello ' + x);

export default sayHello;

export const f = (x) => x * 2;

We export the default sayHello from the module

// we have renamed the import, no curly braces
import saySomething from './exportSample.js';
saySomething('Ali');

As you can see, we are not obliged to keep the same name, and no need for curly braces in default export.

Conclusion

We have seen the difference between module.exports and exports in CJS modules with Node.js, and also the import export in the ESM modules and how to use them, you can also check more in depth about the specifics of ESM in Node.js in their official document.

Slow processing using setTimeo...Promise in JavaScript and how ...


More interesting articles