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:
That means these two lines are equivalent at first:
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.
Another file can then use:
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.
Then:
This works because require(...) returns whatever is stored in module.exports.
The Classic Mistake
This is the bug people run into:
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:
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:
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.exportsis the actual value returned byrequire(...).' - '
exportsstarts as an alias tomodule.exports.' - Mutating
exportsworks; reassigningexportsbreaks the alias. - Use
module.exports = ...when exporting one replacement object, function, or class. - Keep CommonJS rules separate from ES module syntax.

