Isen Kasa

Understanding the Differences Between ESM and CommonJS in Node.js

A comprehensive guide on ESM vs CommonJS in Node.js, with practical examples, migration tips, and best practices.

nodejsesmcommonjs

Understanding the Differences Between ESM and CommonJS in Node.js

Modern JavaScript development has evolved significantly, and one of the biggest shifts has been the move from CommonJS (CJS) to ECMAScript Modules (ESM). If you've ever been confused about when to use require() versus import, this article will clear things up — complete with examples and best practices.


1. What Are Modules?

Modules are reusable pieces of code that can be imported or exported between files. They allow developers to keep code organized and avoid global namespace pollution.

In Node.js, there are two primary module systems:

  1. CommonJS (CJS) – the original Node.js module system
  2. ECMAScript Modules (ESM) – the standardized JavaScript module system adopted from browsers

2. CommonJS Overview

CommonJS has been the default in Node.js since its beginning. It uses the require() function for imports and module.exports for exports.

Example: Using CommonJS

// math.cjs
function add(a, b) {
  return a + b;
}
 
function subtract(a, b) {
  return a - b;
}
 
module.exports = { add, subtract };
// app.cjs
const math = require('./math.cjs');
 
console.log(math.add(2, 3)); // 5

CommonJS Characteristics

  • Synchronous loading (modules are loaded at runtime)
  • require() can be called anywhere in code
  • Uses module.exports and exports for exports
  • Default file extension is .cjs (if mixing with ESM)
  • Historically the default in Node.js

3. ECMAScript Modules (ESM) Overview

ESM is the official module system standardized by JavaScript (ECMAScript 2015+). It uses the import and export syntax.

Example: Using ESM

// math.js
export function add(a, b) {
  return a + b;
}
 
export function subtract(a, b) {
  return a - b;
}
// app.js
import { add } from './math.js';
 
console.log(add(2, 3)); // 5

ESM Characteristics

  • Asynchronous and supports tree-shaking
  • Must use import/export syntax
  • Top-level await is supported
  • File extension is .mjs or .js (with "type": "module" in package.json)
  • Static analysis possible — better for bundlers and performance

4. How to Enable ESM in Node.js

You can enable ESM in Node.js in two ways:

Option 1: Use .mjs Extension

node app.mjs

Option 2: Add to package.json

{
  "type": "module"
}

Then you can use .js files with import and export.


5. Key Differences Between CommonJS and ESM

FeatureCommonJSESM
Syntaxrequire() / module.exportsimport / export
LoadingSynchronousAsynchronous
Default Extension.cjs.mjs or .js
Top-level await❌ Not supported✅ Supported
Tree-shaking❌ No✅ Yes
Static Analysis❌ Harder✅ Easier
Use CaseLegacy or existing Node.js projectsModern apps and libraries

6. Mixed Module Environments

You can still mix CommonJS and ESM, but with limitations.

Importing CommonJS into ESM

// app.js (ESM)
import pkg from './math.cjs';
const { add } = pkg;
console.log(add(2, 3));

Importing ESM into CommonJS

This requires dynamic import:

// app.cjs
(async () => {
  const { add } = await import('./math.js');
  console.log(add(2, 3));
})();

7. Migrating from CommonJS to ESM

If you have an existing CommonJS project, here’s how to migrate:

  1. Add "type": "module" in your package.json.
  2. Rename files from .js to .cjs if you still use require().
  3. Replace:
    const express = require('express');
    with
    import express from 'express';
  4. Export functions using:
    export default MyFunction;
    instead of module.exports = MyFunction;
  5. Ensure paths include extensions (e.g., import './file.js').

8. Example: Same App in Both Systems

CommonJS Version

// app.cjs
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello CJS!'));
app.listen(3000);

ESM Version

// app.js
import express from 'express';
const app = express();
app.get('/', (req, res) => res.send('Hello ESM!'));
app.listen(3000);

9. Which Should You Use?

Use ESM if:

  • You’re starting a new project
  • You want modern syntax and better tooling support
  • You plan to share code with frontend or browser-based code

Use CommonJS if:

  • You maintain older Node.js projects
  • You depend on libraries that only support CJS

Best Practice (2025)

For all new Node.js projects, prefer ESM. It’s the standard moving forward and ensures compatibility with modern tools, frameworks, and JavaScript features.


10. Final Thoughts

While CommonJS has served developers well for over a decade, the JavaScript ecosystem has largely moved toward ESM. Understanding the key differences and knowing how to migrate ensures your projects stay maintainable, modern, and compatible with the evolving JavaScript landscape.


Additional Resources: