← Node & Express

Node Fundamentals

What is the difference between CommonJS and ES Modules in Node.js, and how do you enable each one?

Show answer — try answering out loud first

CommonJS (CJS) is Node's historical module system: it uses require() and module.exports, and loads modules synchronously. ES Modules (ESM) is the official JavaScript standard: it uses import and export, and its loading is asynchronous. You enable ESM by using the .mjs extension or by putting "type": "module" in the package.json. For new projects, ESM is recommended.

A "module" is simply a file that exports things (functions, objects, values) so that other files can use them. Node has two systems for this:

CommonJS (CJS) — Node's original:

  • To export: module.exports or exports.
  • To import: require().
  • Loading is synchronous: when you do require, Node reads and executes that file right then, halting execution until it finishes. This works well because the files are on local disk.
  • require is dynamic: you can call it inside an if or in the middle of a function.

ES Modules (ESM) — the modern JavaScript standard (the same one used in the browser):

  • To export: export and export default.
  • To import: import.
  • Loading is asynchronous: the engine first analyzes all the imports before executing. That's why import (the static form) must go at the top of the file, not inside an if. (There is a dynamic import(), which does return a promise and can be used anywhere.)
  • It's the future: it supports top-level await and is compatible with browsers.

How to enable ESM in Node:

  1. Use the .mjs extension in your files, or
  2. Put "type": "module" in your package.json (this way all .js files are treated as ESM). In that case, if you need an occasional CommonJS file, you use the .cjs extension.

When to use each one?

  • ESM: new projects, code you share with the frontend, when you want top-level await. It's the recommended direction today.
  • CommonJS: old projects or libraries, or when you depend on packages that don't offer an ESM version yet. It's still perfectly valid and very common.
// ===== CommonJS =====

// file: matematicas.cjs
function sumar(a, b) {
  return a + b;
}
module.exports = { sumar }; // we export an object with the function

// file: app.cjs
const { sumar } = require("./matematicas.cjs"); // require is synchronous
console.log(sumar(2, 3)); // 5
// ===== ES Modules =====

// file: matematicas.mjs
export function sumar(a, b) {
  return a + b;
}
export default function restar(a, b) {
  return a - b;
}

// file: app.mjs
import restar, { sumar } from "./matematicas.mjs"; // import goes at the top of the file
console.log(sumar(2, 3)); // 5
console.log(restar(5, 2)); // 3
// Alternative: enable ESM for all .js files via package.json
{
  "name": "mi-proyecto",
  "type": "module"
}

Mixing syntax without noticing: using require() in a file that Node treats as ESM (because it has "type": "module"), which produces the error require is not defined in ES module scope. Or the other way around, using import in a CommonJS file. Another frequent mistake is forgetting the file extension in ESM imports: in Node, import { sumar } from "./matematicas" can fail; you must write "./matematicas.mjs".

"CommonJS is Node's historical system: it uses require and module.exports, and loads modules synchronously, so require can go inside an if or a function. ES Modules is the official JavaScript standard: it uses import and export, its loading is asynchronous, and static imports must go at the top of the file. I enable ESM with the .mjs extension or by putting type module in the package.json, and in that case I use .cjs for the occasional CommonJS files. For new projects I prefer ESM because it's the standard, it works well with the frontend, and it supports top-level await; I keep CommonJS when I'm working with old code or dependencies."

Quick challenge

You have a package.json with "type": "module" and this index.js file. What happens when you run it?

const fs = require("fs");
console.log("hola");
See answer

It fails with an error similar to: ReferenceError: require is not defined in ES module scope.

Since the package.json has "type": "module", Node treats index.js as ESM, and in ESM require doesn't exist. The solution is to use import fs from "fs"; (or import fs from "node:fs";), or else rename the file to index.cjs if you really want to use CommonJS.