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.
// 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)); // 120The 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.
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
| Trait | Consequence |
|---|---|
| Synchronous loading | The file is read when the call runs, which blocks everything else |
| Computed paths allowed | require(someVariable) works, but makes analysis impossible |
| No static analysis | A tool cannot tell what is used, so no Tree shaking |
| Extra variables | __dirname and __filename exist, unlike in ES modules |
Frequently asked questions
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.
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.
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.