Understanding the tricky module.exports and exports in Node.js
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
andmodule.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 animport
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);
- Uses only
exports
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' }
- Uses both
exports
andmodule.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