Skip to content

The difference between module.exports and exports in Node.js

Let’s quickly remind ourselves how to export a CommonJS module, import it and use it in a different file in Node.js runtime. Look at the example.

// greet.js
module.exports.greet = (name) => `Hi ${name} πŸ‘‹`;
// or
exports.greet = (name) => `Hi ${name} πŸ‘‹`;
// index.js
const { greet } = require("./greet");

console.log(greet("Dan")); // Hi Dan πŸ‘‹

Looking at the greet module definition, we achieved the same result using module.exports and exports. It is easy to think that we can use them interchangeably, but can we? Let’s look at the example where module.exports works fine but fail using exports shortcut.

// greet.js
exports = (name) => `Hi ${name} πŸ‘‹`;
// index.js
const greet = require("./greet");

console.log(greet("Dan")); // TypeError: greet is not a function

If this is looking confusing to you, you are not alone. To understand what is going on, we need to grasp how module loader works.

When we call require, the new module is created. Its initial value is an empty object literal {}. Before a module’s code is executed, Node.js will wrap it with the module wrapper. By doing so, we can achieve module scoped variables that don’t leak out to the global object, and we also get access to module-specific variables like module and exports. The exports shortcut is assigned the value of module.exports. Finally, the content of the module.exports is returned β€” this is the public API returned to the caller. A very simplified implementation of the require function looks like this.

function require(/* module */) {
  const module = { exports: {} };

  // the module wrapper
  ((module, exports) => {
    // module's code
  })(module, module.exports);

  return module.exports;

Looking at the snippet above, we can conclude a few things.

Export object literal #

When we attach new member to the exported object, it is safe to use both module.exports and exports shortcut. The public API returned to the caller will reference the same object in both cases.

// greet.js
module.exports.greet = (name) => `Hi ${name} πŸ‘‹`;
// or
exports.greet = (name) => `Hi ${name} πŸ‘‹`;

Export function or primitive value #

When we want to export a function or a primitive value (string, number, etc.), we need to reassign module.exports. Reassigning exports variable is not going to work β€” it will lose bond to the module.exports that is returned to the caller.

// greet.js
module.exports= (name) => `Hi ${name} πŸ‘‹`;

Hopefully, this explanation helped you out. Until next time, stay curious πŸ‘Š

What'ya think?

πŸ‘† you can use Markdown here

Your comment is awaiting moderation. Thanks!