Skip to content

Module Systems (CJS & ESM)

In the beginning, JavaScript had no official way to share code between files. It was the Wild West. When Node.js was built, it needed a way to manage dependencies, so it adopted CommonJS (CJS). This became the standard for years.

Then came ES Modules (ESM)—the official, standardized module system for JavaScript, aimed at unifying the browser and the server. Now, Node.js speaks both languages, but they work quite differently.

Think of it like two different logistics companies operating on the same construction site.

CommonJS is the “Industrial Supply Chain.” It’s practical, synchronous, and built for the server.

When you use require(), you are filling out a Requisition Form and handing it to a clerk who immediately runs to the warehouse, grabs the item, and puts it in your hand before you can do anything else.

  • require(): The order form. It loads the file synchronously.
  • module.exports: The shipping container. Whatever you put in here is what gets shipped.
./app.js
// "Go get the http module right now."
const http = require('http');
// "Go get our tools."
const tools = require('./utils');
./utils.js
const tools = {
hammer: '🔨',
drill: '🔫',
};
// Pack the container
module.exports = tools;

ESM is the “Architectural Blueprint.” It’s static, asynchronous-friendly, and standardized across the entire JS ecosystem (browsers included).

When you use import, you are drawing a dependency line on a blueprint. The engine scans all these lines before running the code to build a complete dependency graph.

  • import: The dependency declaration.
  • export: The public interface.

To use ESM in Node, you either need to use the .mjs extension or add "type": "module" to your package.json.

./app.js
// "I will need these items to function."
import http from 'node:http';
import { hammer } from './utils.js'; // Note the extension!
./utils.js
export const hammer = '🔨';
export const drill = '🔫';
// Or a default export
export default { hammer, drill };

Which one should you use? Here is the breakdown.

FeatureCommonJS (CJS)ES Modules (ESM)
Syntaxrequire / module.exportsimport / export
LoadingSynchronous (Dynamic)Asynchronous (Static Analysis)
Top-Level AwaitNo (requires async wrapper)Yes (very distinctive)
Browser Native?NoYes
__dirnameAvailableNot available (needs polyfill)
Magicrequire can be dynamicimport paths are static

Warning:

You can import CJS into ESM, but you cannot require() an ESM file in CJS.

import module from './commonjs-file.js' - Works!

require('./esm-file.mjs') - Crashes! Use await import() instead

CommonJS vs ESM Handshake


CommonJS vs ES Modules Demo Repo


In Node.js, we’re free from the sandbox of the browser. We can do (almost) anything we want with folders and files on the server.