Modules: ESM and CommonJS
Two module systems
Section titled “Two module systems”Node.js supports two module systems that cannot be freely mixed:
| Feature | ESM | CommonJS |
|---|---|---|
| Syntax | import / export | require() / module.exports |
| File extension | .mjs or .js with "type":"module" | .cjs or .js without "type":"module" |
| Loading | Static, analysed at parse time | Dynamic, executed at runtime |
Top-level await | Supported | Not supported |
| Default in Node 20+ | Yes (opt-in) | Legacy default |
CommonJS (CJS)
Section titled “CommonJS (CJS)”CommonJS was the original Node.js module system. It is still widely used in older packages and tooling.
// math.js (CommonJS)function add(a, b) { return a + b; }function multiply(a, b) { return a * b; }
module.exports = { add, multiply }; // named exports via object// OR: module.exports = add; // single default export
// main.js (CommonJS)const { add, multiply } = require('./math');console.log(add(2, 3)); // 5console.log(multiply(4, 5)); // 20require() is synchronous and loads the file immediately when the line is executed. This means you can require() inside an if block or a function.
ECMAScript Modules (ESM)
Section titled “ECMAScript Modules (ESM)”ESM is the official JavaScript module standard. It is now the recommended system for new Node.js projects.
// math.mjs (ESM — named exports)export function add(a, b) { return a + b; }export function multiply(a, b) { return a * b; }
// utils.mjs (ESM — default export)export default function formatNumber(n) { return n.toFixed(2);}
// main.mjs (ESM — named import)import { add, multiply } from './math.mjs';import formatNumber from './utils.mjs';
console.log(add(2, 3)); // 5console.log(formatNumber(3.14159)); // '3.14'import statements are static — they must appear at the top of the file and are resolved before any code runs. This enables tree-shaking (dead-code elimination) by bundlers.
Enabling ESM in Node.js
Section titled “Enabling ESM in Node.js”Two ways to tell Node.js a file uses ESM:
"type": "module"inpackage.json— all.jsfiles in that package are treated as ESM. Use.cjsfor any file that must stay CommonJS..mjsfile extension — always treated as ESM regardless ofpackage.json.
{ "name": "my-app", "type": "module"}Named vs default exports
Section titled “Named vs default exports”// Named: export multiple bindings, import with exact names (or rename)export const PI = 3.14159;export function area(r) { return PI * r * r; }
import { PI, area } from './circle.mjs';import { area as circleArea } from './circle.mjs'; // rename
// Default: one main export per file, imported with any nameexport default class Logger { /* ... */ }
import Logger from './logger.mjs';import MyLogger from './logger.mjs'; // same thing, different local nameRunnable ESM demo
Section titled “Runnable ESM demo”This snippet runs in the Node.js runtime (module resolution requires Node). It demonstrates how an ES module with named exports works end-to-end using inline dynamic import().
// ESM inline via data: URL — requires Node runtimeconst src = ` export function add(a, b) { return a + b; } export const VERSION = '1.0.0';`;const mod = await import('data:text/javascript,' + encodeURIComponent(src));console.log('add(3, 4):', mod.add(3, 4));console.log('VERSION:', mod.VERSION);Needs the Node.js runtime — open in StackBlitz to run.