Menu

Node.js module.exports vs exports, With Examples

module.exports is the value a CommonJS file hands to whoever calls require() on it. exports is a shortcut that points to the same object, until you reassign it.

This page includes runnable editors. Edit, run, and see output instantly.

module.exports is the value a Node.js file gives to any other file that loads it with require(). It starts as an empty object: add properties to it to export several things, or assign it a new value (a function, a class) to export one thing. exports is a shorthand variable that points to the same object, so exports.add = ... works, but exports = ... breaks the link and exports nothing.

It printed 5 20 3.14159, then [ 'add', 'multiply', 'PI' ]. All three lines in math.js added properties to the same object, whether they went through module.exports or exports, and require('./math') returned that object.

Export a function, a class or an object

When a module has one main thing, assign it to module.exports directly. The caller then gets the function itself, not an object around it:

It printed Hello, Ada and Grace (user). If you see TypeError: greet is not a function, the caller used the wrong shape: write const { greet } = require(...) for an object property and const greet = require(...) for an assigned value.

module.exports vs exports: the reassignment trap

Before your file runs, Node.js sets exports = module.exports. Both names point to one object, and require returns module.exports. Assigning a new value to exports only changes the local variable:

broken.js exported {}, because exports now points to a new object that nothing returns. working.js exported { version: 2 }.

You writeExported?
module.exports.x = 1Yes
exports.x = 1Yes, same object
module.exports = { x: 1 }Yes, replaces the object
exports = { x: 1 }No, the value is lost

The simplest rule: always write module.exports. It works in every case, and the code says what it does.

require returns the same cached object

Node.js runs a module's code once, the first time it is required, and caches module.exports. Every later require of the same file returns that same object, so state inside a module is shared by every file that loads it:

counter.js is running appeared once, b.next() returned 3, and a === b is true, because the cache key is the resolved file path and ./counter and ./counter.js resolve to the same file. That is why a database connection or a config object created in one module is shared across the whole app.

module.exports and ES modules

ES modules use export and import instead. The forms map onto each other like this:

// CommonJS                              // ES modules
module.exports = greet;                  export default greet;
module.exports.add = add;                export { add };
const greet = require('./greet');        import greet from './greet.js';
const { add } = require('./math');       import { add } from './math.js';

An ES module can import a CommonJS file: its module.exports arrives as the default import. Since Node.js 22.12, require() can also load an ES module that has no top-level await. For when to choose which, see CommonJS vs ES modules.

Common mistakes

Mixing exports.x with module.exports = ... in one file. The assignment replaces the object, so every exports.x written before it is gone. Pick one style per file.

Exporting a value that changes later. module.exports = { count } copies the number at load time; when the module updates count, callers still see the old value. Export a function that returns it, as counter.js does above.

Leaving out ./. require('math') looks for a package in node_modules, not for math.js next to your file. Local files always start with ./ or ../.

Frequently Asked Questions

Can I use module.exports and export in the same file?

No. A file is either CommonJS (require, module.exports) or an ES module (import, export). Node.js decides by the extension (.cjs or .mjs) or by "type" in package.json.

Why does require return an empty object?

Usually the module assigned exports = ... instead of module.exports = ..., or two modules require each other. In a circular require, one of them receives the other's module.exports before that file has finished running.

Do I need the .js extension in require?

No. require('./math') tries ./math, then ./math.js, ./math.json and ./math.node, then a folder ./math with an index.js. In an ES module import the extension is required.

How do I reload a module that require already cached?

Delete its entry with delete require.cache[require.resolve('./math')] and require it again. This is mostly useful in tests and dev tools; application code should not rely on it.

Coddy programming languages illustration

Learn to code with Coddy

Get started