Node.js Modules (ES Modules & CommonJS)
- Node.js
- JavaScript
How Node splits code into reusable modules, and the difference between the older CommonJS (require) and the standard ES Modules (import) systems.
What you'll understand
- Why code is split into modules with explicit imports and exports
- The difference between CommonJS (require) and ES Modules (import)
- How Node decides which module system a file uses
Explanation
A module is a single file whose code is private by default. Nothing inside it is visible to other files unless you explicitly export it, and another file gets that value only by importing it. This keeps code organised, prevents accidental name clashes, and makes dependencies between files clear.
Node supports two module systems. CommonJS is the original: you share values with module.exports and load them with require(), which runs synchronously. ES Modules (ESM) are the modern JavaScript standard, shared with the browser: you use export and import, which are statically analysable and support asynchronous loading. New code should prefer ES Modules; CommonJS remains common in older packages and tutorials.
Node decides which system a file uses from a few signals: a .mjs file is always ESM and a .cjs file is always CommonJS, while a plain .js file follows the nearest package.json. If that package.json has "type": "module", .js files are ESM; otherwise they are CommonJS. Mixing the two is possible but a frequent source of confusion, so it is cleanest to pick one per project.
Examples
ES Modules use export and import:
// math.ts
export function add(a: number, b: number) {
return a + b;
}
// main.ts
import { add } from './math.js';
console.log(add(2, 3));The same code in CommonJS uses module.exports and require:
// math.js
function add(a, b) {
return a + b;
}
module.exports = { add };
// main.js
const { add } = require('./math');
console.log(add(2, 3));Common mistakes
- Mixing require and import in the same file and expecting them to interoperate seamlessly.
- Forgetting to export something and then trying to import it (you get undefined).
- Omitting the file extension in relative ES Module imports, which Node requires.
- Assuming a package ships ESM when it only ships CommonJS, or the reverse.
Best practices
- Prefer ES Modules for new projects and set "type": "module" deliberately.
- Export a small, intentional public surface from each module.
- Keep one module system per project to avoid interop headaches.
- Use clear relative paths and include extensions where the module system requires them.
Further reading
- Node.js, ECMAScript modules — https://nodejs.org/api/esm.html
- MDN, JavaScript modules — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules