Module Systems: CommonJS and ES Modules
Objectives
By the end of this lesson, you should be able to:
- Write and use a module in CommonJS (
require/module.exports) - Write and use a module in ES Modules (
import/export) - Explain how Node.js decides which system a file uses, and why mixing them incorrectly fails
💡 Why this matters: Module 2’s
import/exportlesson covered the JavaScript language syntax. This lesson covers Node.js’s two actual, real-world module systems, and the older one, CommonJS, is still extremely common in existing Node.js code and packages.
⚠️ A note on verification: every snippet and output in this lesson was actually run with Node.js.
CommonJS
// math.js
function add(a, b) {
return a + b;
}
module.exports = { add };
// main.js
const { add } = require('./math.js');
console.log(add(2, 3));
5
CommonJS is Node’s original module system, module.exports exposes values from a file, require() loads them in another file. require() is synchronous, it loads and returns a module’s exports immediately, blocking until it’s done, this is part of why it can’t directly load an ES Module, covered below.
ES Modules
// math.js
export function add(a, b) {
return a + b;
}
// main.js
import { add } from './math.js';
console.log(add(2, 3));
5
This is the same import/export syntax from Module 2, now actually running in Node.js. ES Modules are the standardized JavaScript module system, the same one browsers use natively.
How Node.js Decides Which System to Use
Node.js needs to know, per file, which system it’s using. Two ways to declare it: a .mjs file extension always means ES Modules, a .cjs extension always means CommonJS, regardless of anything else. For plain .js files, Node checks the nearest package.json’s "type" field, "type": "module" treats every .js file in that project as an ES Module, no "type" field (or "type": "commonjs") treats them as CommonJS, Node’s original default.
Mixing Them Incorrectly Fails
// package.json has "type": "module"
// uses-require.js
const fs = require('fs');
console.log(fs);
file:///tmp/node103/mixed-demo2/uses-require.js:1
const fs = require('fs');
^
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and 'package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
Once a file is being treated as an ES Module, require genuinely doesn’t exist in it, require is a CommonJS-specific global, not part of the ES Module system at all. Node’s error message here is unusually direct about the fix: rename the file to .cjs, or use import instead.
Choosing One for a Project
Mixing both systems in the same project is possible but adds real complexity. Most new Node.js projects, including the Express projects built starting in Module 5, pick one system, commonly ES Modules for new code, matching the syntax already familiar from Module 2, and stick with it consistently.
Try It
- Create a small CommonJS module with
module.exports, and use it from another file withrequire(). - Create a small ES Module with
export, set"type": "module"inpackage.json, and use it from another file withimport. - Try using
require()inside a file governed by"type": "module", and read the resulting error carefully. - Explain, in your own words, how Node.js decides whether a given
.jsfile is CommonJS or an ES Module.
Recap
- CommonJS uses
require()andmodule.exports, Node’s original, still widely used module system. - ES Modules use
import/export, the standardized system, the same one browsers use. - A
.mjs/.cjsextension, or apackage.json’s"type"field, determines which system a.jsfile uses, andrequiresimply doesn’t exist inside a file being treated as an ES Module.
Next lesson: environment variables, configuring a program without hardcoding values.