CommonJS vs ESM
Overview
For its first 10 years, Node.js did not have a native module system. The creators invented CommonJS (CJS) (require() and module.exports), which loads files synchronously. Meanwhile, the Official JavaScript standard (TC39) invented ES Modules (ESM) (import and export), which loads files asynchronously for the browser. Today, Node.js supports both, but they are fundamentally incompatible. ESM is the modern future, but millions of legacy NPM packages still use CommonJS.
Syntax
/* --- 1. CommonJS (CJS) - The Legacy Node Standard --- */
// math.js
module.exports = function add(a, b) { return a + b; }
// app.js
const add = require('./math.js'); // Synchronous and dynamic!
console.log(add(2, 2));
/* --- 2. ES Modules (ESM) - The Modern JS Standard --- */
// math.mjs (or type: "module" in package.json)
export function add(a, b) { return a + b; }
// app.mjs
// Asynchronous, statically analyzed at compile time!
import { add } from './math.mjs';
console.log(add(2, 2));Common Pitfalls
- Mixing
importandrequirein the same file. You cannot freely mix them. If a file is an ES Module (you usedimport), you cannot userequire()inside it natively. If you try, Node will throw a fatal error. - Forgetting file extensions in ESM. In CommonJS,
require('./math')automatically searches for.jsor/index.js. In native Node ESM, the module resolution is strictly literal. You MUST writeimport { add } from './math.js'. (Though bundlers like Webpack/Vite often hide this rule).
Interview Questions
await fetch(...) outside of an async function) possible in ES Modules, but completely impossible in CommonJS?CommonJS require() is strictly synchronous; it blocks the thread until the file is fully read and evaluated. ESM import is inherently asynchronous in its parsing phase, allowing the V8 engine to pause execution (await) during module initialization before handing the module back to the caller.
Real-World Example
How to force a Node.js project to use modern ES Modules universally.
// Inside your package.json, add this single line:
{
"name": "my-modern-api",
"version": "1.0.0",
"type": "module", // Every .js file is now treated as an ES Module!
"dependencies": {
"express": "^4.18.2"
}
}Check Your Knowledge
Test your understanding of CommonJS vs ESM with these quick questions.