JavaScript Modules: CommonJS vs ESM and Why Modules Matter
When a JavaScript application is small, it is possible to keep everything inside a single file. But as an application grows, putting all the code in one file becomes difficult to understand and maintain.
For example, imagine an application containing:
- User authentication
- Database operations
- Payment processing
- Notification services
- Utility functions
- API routes
If all of this code is placed in one JavaScript file, the file can quickly become difficult to manage.
This is where JavaScript modules become important.
What is a Module?
A module is a separate, self-contained piece of JavaScript code that handles a particular responsibility.
For example, instead of having one large file:
app.js
we can organize the application as:
project/
│
├── app.js
├── user.js
├── database.js
├── payment.js
└── utils.js
Each file becomes responsible for a particular part of the application.
The important idea is:
A module allows us to organize code into separate units and share only the functionality that other parts of the application need.
Why Do We Need Modules?
Modules solve several problems.
1. Code Organization
Instead of putting everything into one file, related functionality can be grouped together.
For example:
user.js
can contain user-related functionality, while:
database.js
contains database-related functionality.
2. Reusability
Suppose we create an add() function.
Instead of rewriting it in multiple files, we can export it from one module and reuse it.
3. Avoiding Global Variables
Without modules, variables can accidentally become globally accessible.
Modules provide a controlled scope for variables and functions.
4. Maintainability
When functionality is separated into modules, changing one part of the application becomes easier.
For example, if database logic is inside:
database.js
we don't need to search through the entire application to find database-related code.
CommonJS
CommonJS is a module system traditionally associated with Node.js.
It uses:
require()
for importing modules and:
module.exports
for exporting functionality.
Creating a CommonJS Module
Suppose we have:
math.js
Inside it:
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = {
add,
subtract
};
Here we are exporting both functions.
Now another file can use them.
const math = require("./math");
console.log(math.add(10, 5));
console.log(math.subtract(10, 5));
Output:
15
5
The important flow is:
math.js
|
| module.exports
↓
Exports functions
|
↓
app.js
|
| require()
↓
Uses functions
ESM — ECMAScript Modules
ESM stands for ECMAScript Modules.
It is the standardized JavaScript module system and uses:
export
and:
import
Creating an ESM Module
math.js:
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
Then:
import { add, subtract } from "./math.js";
console.log(add(10, 5));
console.log(subtract(10, 5));
Output:
15
5
The concept is the same as CommonJS:
One file
↓
exports functionality
↓
Another file
↓
imports functionality
Only the syntax and module system are different.
Named Exports
With ESM, we can export multiple named values.
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
Import them using their names:
import { add, multiply } from "./math.js";
The names need to match the exported names unless aliases are used.
Default Export
ESM also supports a default export.
export default function greet(name) {
return `Hello ${name}`;
}
Then:
import greet from "./greet.js";
console.log(greet("Sai"));
With a default export, the importing code can choose the local name.
CommonJS vs ESM
The main difference can be understood from the syntax.
CommonJS
Export:
module.exports = {
add
};
Import:
const { add } = require("./math");
ESM
Export:
export function add(a, b) {
return a + b;
}
Import:
import { add } from "./math.js";
Comparison
| Feature | CommonJS | ESM |
|---|---|---|
| Import | require() |
import |
| Export | module.exports |
export |
| Standard | Node.js module system | JavaScript standard |
| Common usage | Traditional Node.js projects | Modern JavaScript |
| Default syntax | require("./file") |
import x from "./file.js" |
The important thing is not to memorize the table.
Remember:
CommonJS →
require+module.exportsESM →
import+export
Why This Matters in Node.js
Modern Node.js applications commonly use ESM, but CommonJS is still widely used in existing Node.js projects.
For example, a project may have:
const express = require("express");
which indicates CommonJS.
Another project may have:
import express from "express";
which indicates ESM.
Both approaches allow modules to be created and reused.
The important thing when working on a project is to understand which module system the project is configured to use.
Modules and Separation of Responsibility
One of the biggest benefits of modules is separation of responsibility.
Imagine an employee-management application.
Instead of:
server.js
|
├── Database code
├── Employee code
├── Authentication code
├── Validation code
└── Utility code
we can organize it:
server.js
|
├── database.js
├── employee.js
├── auth.js
├── validation.js
└── utils.js
Now each module has a clear responsibility.
This idea becomes extremely important when we start learning design patterns.
Connection to Design Patterns
Modules and design patterns solve different problems.
Modules answer:
"How should I organize and share my code?"
Design patterns answer:
"How should I structure my code to solve a recurring design problem?"
For example:
Modules
↓
Organize code
Design Patterns
↓
Structure solutions
Open/Closed Principle
↓
Make code easier to extend
These concepts work together when building larger applications.
A Simple Real-World Example
Imagine an online shopping application.
We might have:
shop/
│
├── user.js
├── product.js
├── cart.js
├── payment.js
└── notification.js
notification.js might export notification functionality.
Later, we might use a Factory Pattern inside that module to create:
Email Notification
SMS Notification
Push Notification
So modules give us the structure, while design patterns help us solve specific design problems inside that structure.
Key Takeaways
From this topic, the most important concepts are:
- Modules divide an application into smaller, manageable pieces.
- Modules improve organization, reuse, and maintainability.
-
CommonJS uses
require()andmodule.exports. -
ESM uses
importandexport. - ESM is the standardized JavaScript module system.
- Modules help separate responsibilities.
- Modules and design patterns are different concepts, but they work together to create maintainable applications.
The most important mental model is:
Large Application
↓
Divide into
Modules
↓
Organize responsibilities
↓
Use Design Patterns
↓
Solve recurring design problems
↓
Maintainable Application
This understanding of modules provides the foundation for the next part of Week-06: design patterns such as Singleton, Factory, and Observer.
Top comments (0)