Understanding the tricky module.exports and exports in Node.js

Ibrahim AlRayyan
5 min readApr 9, 2021

What is a Module?

A module is a separate program file in Node.js. When creating a module, it can be classified as a grouping of all related functions into a file.

getYear = function() {
return new Date().getFullYear();
}
getMonth = function() {
return new Date().getMonth();
}

Purpose of a Module

Keeping all functional programs in a single file is really hard to maintain and deal with during the development. Having the code split up into multiple accessible files allows us to have appropriate file names for every file. This way you can easily identify the module and know where to find it at times of need. Having modules makes it easier to find certain parts of code which makes it more maintainable.

Most common Module Formats

There are many module formats in JavaScript but there are two formats that used almost in all javascript projects:

  • The CommonJS (CJS) format is used in Node.js and uses require and module.exports to define dependencies and modules. The npm ecosystem is built upon this format.
  • The ES Module (ESM) format. As of ES6 (ES2015), JavaScript supports a native module format. It uses an export keyword to export a module’s public API and an import keyword to import it.

What we will talk about in this article is related to CommonJS format

Requiring a Module

Node.js comes with a set of built-in modules that we can use in our code without having to install them. To do this, we need to require the module using the require keyword and assign the result to a variable.

you can use the file system module and its readdir method:

const fs = require('fs');
const folderPath = '/home/jim/Desktop/';
fs.readdir(folderPath, (err, files) => {
files.forEach(file => {
console.log(file);
});
});

Note that in CommonJS, modules are loaded synchronously and processed in the order they occur.

Creating and Exporting a Module

create our own module and export it for use elsewhere in our program.

// user.js
const getName = () => {
return 'Jim';
};
exports.getName = getName;

The other module is index.js

// index.js
const user = require('./user');
console.log(`User: ${user.getName()}`);

Exporting Multiple Methods and Values

We can export multiple methods and values in the same way:

// user.js
const getName = () => {
return 'Jim';
};
const getLocation = () => {
return 'Munich';
};
const dateOfBirth = '12.01.1982';
exports.getName = getName;
exports.getLocation = getLocation;
exports.dob = dateOfBirth;

indxe.js

// index.js
const user = require('./user');
console.log(
`${user.getName()} lives in ${user.getLocation()} and was born on ${user.dob}.`);

Notice how the name we give the exported dateOfBirth variable can be anything we fancy (dob in this case). It doesn’t have to be the same as the original variable name.

Variations in Syntax

I should also mention that it’s possible to export methods and values as you go, not just at the end of the file. For example:

exports.getName = () => {
return 'Jim';
};
exports.getLocation = () => {
return 'Munich';
};
exports.dob = '12.01.1982';

And thanks to destructuring assignment, we can cherry-pick what we want to import:

const { getName, dob } = require('./user');
console.log(
`${getName()} was born on ${dob}.`);

Exporting a Default Value

when you have a module that exports just one thing, it’s more common to use module.exports, but for sure you can use just `exports`:

// user.js
class User {
constructor(name, age, email) {
this.name = name;
this.age = age;
this.email = email;
}
getUserStats() {
return `
Name: ${this.name}
Age: ${this.age}
Email: ${this.email}
`;
}
}
module.exports = User;// index.js
const User = require('./user');
const jim = new User('Jim', 37, 'jim@example.com');
console.log(jim.getUserStats());

What’s the Difference Between module.exports and exports?

create file index.js and run it using node

console.log(module);

the output:

Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/yaapa/projects/hacksparrow.com/run.js',
loaded: false,
children: [],
paths:
[ '/Users/yaapa/projects/hacksparrow.com/node_modules',
'/Users/yaapa/projects/node_modules',
'/Users/yaapa/node_modules',
'/Users/node_modules',
'/node_modules' ] }

So it looks like module is a contextual reference to the file you just executed.

Did you notice it has an exports property? Which is an empty object. That's where you define the 'importable' objects of your module.

Now edit index.js and run it again:

// index.js
exports.firstName = "Ibrahim";
exports.lastName = "AlRayyan";
Module {
id: '.',
exports: { firstName: 'Ibrahim', lastName: 'AlRayyan' },
...

You can see that assigning properties to the exports object has added those properties to modules.exports.

That is because exports is a reference to modules.exports.

You can confirm it with the following code:

// index.js
exports.firstName = "Ibrahim";
console.log(exports === module.exports);
console.log(module.exports);

The output will be:

true
{ firstName: 'Ibrahim' }

Let’s now explain the real difference between them by wring some codes in two separate files.

I’ll use two files user.js and index.js where index.js will never change:

// index.js
const fullName = require('./user');
console.log(fullName);
  1. Uses onlyexports in exporting (source) file → user.js :
// user.js
const lastName = "AlRayyan";
const firstName = "Ibrahim";
exports.firstName = firstName;
exports.lastName = lastName;

The output will be:

{ firstName: 'Ibrahim', lastName: 'AlRayyan' }
  1. Uses both exports and module.exports in the same source file:
// user.js
const lastName = "AlRayyan";
const firstName = "Ibrahim";
module.exports = {x: "x"};
exports.lastName = lastName;

The output will be:

{ x: 'x' }

So, using module.exports and exports in the same file overrides the value of exports. And should not use both of them in the same file or module.

Other useful things to know

When the require() function is called to the same module in multiple files, the module has to be loaded once only. Next time a require() function is called on the same module then pulls from the cache.
The require() function loads modules synchronously. Suppose, it loads modules asynchronously, we could not access the objects in the right way. So, synchronous behavior is required while requiring modules from one file to another.

I hope you have found this useful. You can start to test these code snippets to get a clear understanding of this concept.

Refernces

  1. https://www.sitepoint.com/understanding-module-exports-exports-node-js/
  2. https://www.hacksparrow.com/nodejs/exports-vs-module-exports.html
  3. https://www.agiratech.com/blog/understanding-module-exports-and-exports-in-node-js/

--

--

Ibrahim AlRayyan

React Developer, Mobile cross-platform developer, JavaScript Lover