Back to Blog
Lesson 7 of the Node.js: Build Your First Server & CLI course
Node.jsJuly 25, 20264 min read

CommonJS vs ES Modules: Mastering Node.js Module Systems

Learn the difference between CommonJS and ES Modules in Node.js. Master require, module.exports, import, and export to structure your backend projects correctly.

Node.jsJavaScriptCommonJSESMModulesBackend
Close-up of an antique radio tuner with knobs and a retro display, evoking a classic analog feel.

Previously in this course, we explored Introduction to Modules: Organizing Your Node.js Code, where we covered the basics of splitting your logic into separate files using the legacy require syntax. Today, we bridge the gap between that foundation and the modern web standard: ECMAScript Modules (ESM).

Node.js has a unique history: it grew up with its own module system (CommonJS), while the browser evolved with a different, standardized one (ES Modules). As a modern developer, you will encounter both in production environments. Understanding how to use both—and how to tell Node.js which one you're using—is essential for building scalable applications.

Understanding the Two Standards

At a high level, CommonJS (CJS) is the "Node.js way," while ES Modules (ESM) is the "JavaScript way."

CommonJS is synchronous. When you require a file, Node.js pauses execution, loads the file, and returns the exported object immediately. This was perfect for server-side development in 2009. ES Modules, however, are designed to be asynchronous and static, allowing for better optimization by tools like bundlers.

FeatureCommonJS (CJS)ES Modules (ESM)
Syntaxrequire() / module.exportsimport / export
LoadingSynchronousAsynchronous
Default.js (in older versions).mjs or type: "module"
Top-level awaitNoYes

Using CommonJS (The Legacy Standard)

Close-up of software development tools displaying code and version control systems on a computer monitor.

In CommonJS, you attach functionality to an object called module.exports. When you need that code elsewhere, you use the require function.

math.js

JAVASCRIPT
const add = (a, b) => a + b;
module.exports = { add };

app.js

JAVASCRIPT
const { add } = require(CE9178">'./math.js');
console.log(add(2, 3));

This works out of the box in any Node.js environment without extra configuration.

Using ES Modules (The Modern Standard)

ES Modules use the import and export keywords. To enable this in your Node.js project, you must explicitly tell Node.js that your files are modules.

1. Configuring package.json

Open the package.json file you created in our earlier lesson on Initializing Projects with NPM. Add the "type": "module" field to the root of your JSON object:

JSON
{
  "name": "my-server-app",
  "version": "1.0.0",
  "type": "module",
  "dependencies": { ... }
}

Once this is set, Node.js will treat every .js file in your project as an ES Module.

2. Exporting and Importing

Now you can use the clean, standardized syntax:

math.js

JAVASCRIPT
export const add = (a, b) => a + b;

app.js

JAVASCRIPT
import { add } from CE9178">'./math.js';
console.log(add(2, 3));

Worked Example: Refactoring our Project

We are building a REST API. Let's imagine we have a db.js file that connects to our database. If we were using CommonJS, we might have module.exports = connectDB. To switch to ESM:

  1. Update package.json with "type": "module".
  2. Change const connectDB = require('./db.js') to import connectDB from './db.js'.
  3. Change module.exports = connectDB to export default connectDB.

Hands-on Exercise

  1. Create a file named greetings.js.
  2. Inside, use export const hello = (name) => "Hello, " + name;.
  3. Create an index.js file.
  4. Import the hello function using the import statement.
  5. Run your script with node index.js. Note: If you get a "Cannot use import statement outside a module" error, double-check your package.json.

Common Pitfalls

  • Missing File Extensions: Unlike CommonJS, ES Modules require you to include the file extension in your import paths (e.g., import { x } from './utils.js' instead of ./utils).
  • Mixing Systems: You cannot use require inside a file marked as type: "module". Conversely, you cannot use import in a default CJS file without using dynamic import() calls.
  • __dirname and __filename: These variables do not exist in ES Modules. You must recreate them using import.meta.url if you need path resolution.

FAQ

Q: Should I always use ES Modules? A: Yes, for new projects. It is the future of JavaScript and is supported by both browsers and modern Node.js versions.

Q: Can I use import without package.json? A: You can rename your files from .js to .mjs. Node.js treats .mjs files as ES Modules regardless of your package.json configuration.

Q: What if I have a dependency that only uses CommonJS? A: You can still import CommonJS packages into an ES Module project using default imports. Node.js handles the compatibility layer for you.

Recap

We've explored the differences between the legacy CommonJS system and the modern ES Modules standard. By updating your package.json to include "type": "module", you unlock the ability to use standard import and export syntax, keeping your code clean and forward-compatible.

Up next: We'll dive into the path module to handle file system locations reliably across different operating systems.

Similar Posts