Node.js
module.exports
exports
JavaScript
Web Development

module.exports vs exports in Node.js

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In CommonJS modules, module.exports is the real object returned by require(...). exports starts as a convenient alias to that same object, which is why adding properties through either reference works until you reassign exports itself.

The Important Relationship

At the start of a CommonJS module, Node effectively gives you something like:

javascript
let module = { exports: {} };
let exports = module.exports;

That means these two lines are equivalent at first:

javascript
exports.sayHi = () => "hi";
module.exports.sayHi = () => "hi";

In both cases, you are mutating the same exported object.

When exports Works Fine

If you are adding several properties to the export object, exports is convenient shorthand.

javascript
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;

Another file can then use:

javascript
const math = require("./math");
console.log(math.add(2, 3));

This is fine because exports still points at module.exports.

When You Must Use module.exports

If you want the module to export a single function, class, or replacement object, assign it to module.exports.

javascript
module.exports = function greet(name) {
  return `Hello, ${name}`;
};

Then:

javascript
const greet = require("./greet");
console.log(greet("Ada"));

This works because require(...) returns whatever is stored in module.exports.

The Classic Mistake

This is the bug people run into:

javascript
exports = function greet(name) {
  return `Hello, ${name}`;
};

That does not replace the exported value. It only reassigns the local exports variable, leaving module.exports unchanged.

So this module exports an empty object, not your function.

A Small Demonstration

Consider this module:

javascript
1console.log(exports === module.exports); // true
2
3exports.value = 1;
4console.log(exports === module.exports); // true
5
6exports = { value: 2 };
7console.log(exports === module.exports); // false
8
9console.log(module.exports); // { value: 1 }

The first property assignment mutates the shared object. The reassignment breaks the alias.

A Good Rule of Thumb

Use:

  • 'exports.foo = ... when you are adding named properties'
  • 'module.exports = ... when you are replacing the whole export'

That simple rule avoids most confusion.

CommonJS Versus ES Modules

This topic applies to CommonJS, which uses require and module.exports. It is different from ES modules, which use export and import.

For example:

javascript
1// ES module syntax
2export function add(a, b) {
3  return a + b;
4}

Do not mix the syntax mentally. exports is a CommonJS concept, not an ES module keyword.

A Practical Team Convention

Many teams adopt a simple convention to avoid confusion:

  • use module.exports = ... when the file has one primary thing to export
  • use exports.name = ... when the file exports a small collection of named helpers

That keeps the module shape obvious to readers before they even scroll through the whole file.

Common Pitfalls

The biggest mistake is assigning directly to exports and expecting require(...) to return that new value. It will not, because module.exports is the real export target.

Another issue is mixing exports.foo = ... and later module.exports = ... in the same file without realizing the earlier property assignments may be discarded by the full replacement.

Developers also confuse CommonJS and ES module syntax. The rules for exports and module.exports do not carry over to export default or named ES exports.

Finally, remember that exports is only a convenience alias. When in doubt, inspect module.exports because that is what Node actually returns.

Summary

  • 'module.exports is the actual value returned by require(...).'
  • 'exports starts as an alias to module.exports.'
  • Mutating exports works; reassigning exports breaks the alias.
  • Use module.exports = ... when exporting one replacement object, function, or class.
  • Keep CommonJS rules separate from ES module syntax.

Course illustration
Course illustration

All Rights Reserved.