CommonJS in JavaScript: require, module.exports, and Node's legacy

CommonJS is Node's older module system: require to load, module.exports to publish. Here are its rules and how it coexists with ES modules today.
3 min read
Believemy logo

Before the language had modules of its own, Node still needed a way to split a program across files. It adopted CommonJS, and millions of packages were published in that shape.

The result is still visible today: most server projects meet both systems, and knowing which one applies removes a good half of the error messages that appear on startup.


Definition

CommonJS is a module system in which a file publishes its values by placing them on the module.exports object, and pulls in others by calling the require() function. Unlike a ES module, everything happens at run time: require() is a function call like any other.

JAVASCRIPT
// cart.js
const VAT = 0.2;

function totalWithTax(net) {
  return net * (1 + VAT);
}

module.exports = { VAT, totalWithTax };

// invoice.js
const { totalWithTax } = require('./cart.js');

console.log(totalWithTax(100)); // 120

The extension may be omitted, and a path with no leading dot names an installed package, exactly as with import.


module.exports versus exports, the classic mix-up

On startup, exports is nothing more than a shortcut to module.exports. Adding a key to one therefore adds it to the other, and all is well.

The trap springs the moment the whole object is replaced. module.exports = { b: 2 } cuts the link: whatever had been added to exports earlier vanishes, and the file doing the require receives only { b: 2 }. The rule is simple: pick one of the two spellings and hold it across the entire file.

Good to know

require() caches every module. A second call on the same path does not re-run the file, it hands back the object already built. That is what allows state to be shared between files, deliberately or by accident.


What CommonJS gives, and what it costs

TraitConsequence
Synchronous loadingThe file is read when the call runs, which blocks everything else
Computed paths allowedrequire(someVariable) works, but makes analysis impossible
No static analysisA tool cannot tell what is used, so no Tree shaking
Extra variables__dirname and __filename exist, unlike in ES modules


Frequently asked questions

Question

How do I know whether a file is treated as CommonJS?

By default a .js file is CommonJS, unless the nearest package.json declares "type": "module". The extension settles it in every case: .cjs forces CommonJS, .mjs forces ES modules. When in doubt, an explicit extension ends the argument for good.


Question

What does the "require is not defined" error mean?

It says the file is being run as an ES module, where require does not exist. Two ways out: convert the file to import, which is the better move on a new project, or rename it to .cjs when the surrounding code is not ready to change.


Question

Is CommonJS obsolete?

It has not been abandoned, and a huge share of published packages is still written that way. For new code, ES modules are the default choice: they belong to the language, they also run in the browser, and they open the door to Bundler optimizations.

Related terms

Discover our javaScript glossary

Every word of JavaScript explained simply: keywords, built-in objects, methods, errors and concepts. Clear definitions and examples that actually run, to learn and to troubleshoot.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.