Introduction: Two Languages Inside the Same Company
Imagine AQAD started its journey in 2018.
At that time, all employees communicated using English.
Every document, report, and process followed English.
The company grew successfully.
Years later, AQAD expanded globally.
Now many international partners preferred another communication standard.
Instead of forcing everyone to use the old system forever, AQAD began supporting both systems.
Old employees continued using the original approach.
New employees adopted the newer approach.
For a period of time, both systems existed together.
This is exactly what happened in Node.js.
For many years Node.js used:
CommonJS
const fs = require('fs');
module.exports = {};
Later JavaScript introduced a modern module system:
ES Modules (ESM)
import fs from 'fs';
export default {};
Today every Node.js developer must understand both.
Because in real projects you will encounter:
- Old Node.js applications using CommonJS
- Modern applications using ES Modules
- Libraries supporting both systems
- Migration projects converting from CommonJS to ES Modules
Before becoming a backend developer, you need to understand why both exist and when to use each one.
The Problem Before Modules Existed
Let's go back to the early days of JavaScript.
Developers often included files directly in HTML:
<script src="user.js"></script>
<script src="product.js"></script>
<script src="order.js"></script>
This worked for small applications.
But large applications faced problems.
Problem 1: Global Variable Pollution
user.js
var name = "Ahmed";
product.js
var name = "iPhone";
Which value should JavaScript use?
Nobody knows.
Variables start colliding.
Problem 2: Dependency Management
Suppose:
product.js
depends on:
database.js
If files load in the wrong order:
<script src="product.js"></script>
<script src="database.js"></script>
Application breaks.
Problem 3: Maintainability
As projects grow:
50 Files
100 Files
500 Files
Managing dependencies becomes difficult.
Developers needed a better solution.
Birth of CommonJS
When Node.js was created in 2009, JavaScript had no official module system.
Ryan Dahl needed a solution.
The Node.js community adopted:
CommonJS
It became the standard module system for Node.js.
What Is CommonJS?
CommonJS is the original module system used by Node.js.
It introduced two major concepts:
Export
Share code.
Require
Import code.
CommonJS Example
math.js
function add(a, b) {
return a + b;
}
module.exports = add;
app.js
const add = require('./math');
console.log(add(5, 10));
Output:
15
Simple.
Powerful.
Easy to understand.
This became the foundation of Node.js development.
How CommonJS Works Internally
When Node.js encounters:
require('./math');
it performs these steps:
Step 1
Locate file.
Step 2
Read file.
Step 3
Wrap file inside a function.
Internally Node.js does something similar:
(function (
exports,
require,
module,
__filename,
__dirname
) {
// module code
});
Step 4
Execute code.
Step 5
Return exported value.
This wrapping mechanism explains why:
module
exports
require
__dirname
__filename
are available inside every CommonJS module.
Why CommonJS Became Popular
At the time it solved many problems.
Benefits:
Simplicity
Easy syntax.
require()
Reusability
Modules could easily share functionality.
Encapsulation
Variables stayed inside modules.
Better Project Structure
Large applications became manageable.
Limitations of CommonJS
As JavaScript evolved, developers discovered limitations.
Limitation 1: Synchronous Loading
CommonJS loads modules synchronously.
const user = require('./user');
Node waits until loading completes.
For server-side development this is acceptable.
For browsers it can be inefficient.
Limitation 2: Not Native JavaScript
CommonJS was created by the community.
Not by the JavaScript language itself.
Limitation 3: Browser Compatibility
Browsers did not natively understand:
require()
Extra tools became necessary.
Examples:
- Webpack
- Browserify
- Rollup
The JavaScript ecosystem needed an official standard.
Birth of ES Modules
In 2015, JavaScript introduced:
ECMAScript 6 (ES6)
One major feature was:
ES Modules (ESM)
For the first time, JavaScript officially supported modules.
What Are ES Modules?
ES Modules are the official module system of JavaScript.
Instead of:
require()
we use:
import
Instead of:
module.exports
we use:
export
Your First ES Module
math.js
export function add(a, b) {
return a + b;
}
app.js
import { add } from './math.js';
console.log(add(5, 10));
Output:
15
Same result.
Different syntax.
Exporting in ES Modules
There are multiple ways.
Named Export
math.js
export function add() {}
export function subtract() {}
Import:
import {
add,
subtract
} from './math.js';
Default Export
Sometimes a module exposes only one main item.
Example:
export default function add() {
}
Import:
import add from './math.js';
Notice:
No curly braces.
AQAD Example
Imagine:
paymentService.js
contains a single payment processor.
Export:
export default processPayment;
Import:
import processPayment
from './paymentService.js';
This is very common in modern projects.
Multiple Named Exports
userService.js
export function createUser() {}
export function updateUser() {}
export function deleteUser() {}
Import:
import {
createUser,
updateUser,
deleteUser
}
from './userService.js';
This pattern is heavily used in enterprise applications.
CommonJS vs ES Modules Syntax
CommonJS
const fs = require('fs');
module.exports = {};
ES Modules
import fs from 'fs';
export default {};
Same goal.
Different syntax.
Enabling ES Modules in Node.js
Node.js originally supported only CommonJS.
To use ES Modules, we must tell Node.js.
Method 1: package.json
{
"type": "module"
}
Now Node treats files as ES Modules.
Example
package.json
{
"type": "module"
}
math.js
export function add(a, b) {
return a + b;
}
app.js
import { add }
from './math.js';
Works correctly.
Important Difference: File Extensions
CommonJS:
require('./math');
Often works without extension.
ES Modules:
import { add }
from './math.js';
Extension is required.
This surprises many beginners.
CommonJS Export Example
user.js
module.exports = {
getUser() {},
createUser() {}
};
Import:
const user =
require('./user');
ES Module Equivalent
user.js
export function getUser() {}
export function createUser() {}
Import:
import {
getUser,
createUser
}
from './user.js';
Much cleaner and easier to read.
CommonJS vs ES Modules Comparison
| Feature | CommonJS | ES Modules |
|---|---|---|
| Import | require() | import |
| Export | module.exports | export |
| Official JS Standard | No | Yes |
| Browser Support | No | Yes |
| Node.js Support | Yes | Yes |
| Static Analysis | Limited | Excellent |
| Tree Shaking | Poor | Excellent |
| Modern Usage | Declining | Growing |
What Is Tree Shaking?
Imagine AQAD ships a package containing:
100 Products
Retailer only needs:
10 Products
Would you deliver all 100?
No.
You send only what's required.
Tree shaking works similarly.
ES Modules allow build tools to remove unused code.
Result:
- Smaller bundles
- Faster applications
- Better performance
CommonJS in Real Backend Projects
Many production Node.js systems still use:
require()
module.exports
Why?
Because:
- Stable
- Mature
- Large existing codebases
Many companies built applications years before ES Modules existed.
ES Modules in Modern Projects
New projects increasingly use:
import
export
because:
- Standard JavaScript
- Better tooling
- Better optimization
- Cleaner syntax
AQAD Example
Suppose AQAD starts today.
A modern architecture might use:
import express from 'express';
import userRoutes
from './routes/userRoutes.js';
import orderRoutes
from './routes/orderRoutes.js';
This follows current industry trends.
Mixing CommonJS and ES Modules
Beginners often attempt:
import express
from 'express';
module.exports = {};
Bad idea.
Mixing systems creates confusion.
Choose one approach per project.
Common Beginner Mistakes
Mistake 1
Forgetting "type": "module"
import fs from 'fs';
Error appears.
Why?
Node still expects CommonJS.
Mistake 2
Missing File Extension
Bad:
import user
from './user';
Good:
import user
from './user.js';
Mistake 3
Using require Inside ES Module
Bad:
import express
from 'express';
const fs = require('fs');
Avoid mixing.
Mistake 4
Confusing Default and Named Exports
Export:
export default add;
Import:
import add from './math.js';
Not:
import { add }
from './math.js';
Mini Exercises
Exercise 1
Create:
export function greet() {}
Import it into another file.
Exercise 2
Create:
export default function multiply() {}
Import and execute it.
Exercise 3
Convert a CommonJS module into an ES Module.
Exercise 4
Create:
userService.js
with:
createUser()
deleteUser()
updateUser()
Import all three.
Try It Yourself
Create:
package.json
{
"type": "module"
}
math.js
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
app.js
import {
add,
multiply
}
from './math.js';
console.log(add(5, 5));
console.log(multiply(5, 5));
Run:
node app.js
Observe how ES Modules work.
Real Industry Recommendation
If you are maintaining an old Node.js application:
Use CommonJS.
If you are starting a new project today:
Prefer ES Modules.
Most modern frameworks and tools are moving toward:
import
export
as the standard approach.
Learning both is important because you will encounter both throughout your backend career.
NPM Deep Dive – Understanding the World's Largest Software Library
Introduction: Imagine Building AQAD Without Suppliers
Let's imagine a strange situation.
AQAD launches a new marketplace.
Retailers open the app and search for products.
But there is a problem.
There are no suppliers.
No vendors.
No manufacturers.
No distributors.
Every single product must be created by AQAD itself.
Need rice?
Produce it yourself.
Need milk?
Create your own dairy farm.
Need soft drinks?
Build your own beverage factory.
Need electronics?
Open your own manufacturing plant.
Very quickly the business becomes impossible to scale.
Now let's compare this to software development.
Imagine building a Node.js application.
Need authentication?
Write everything from scratch.
Need password encryption?
Write your own algorithm.
Need JWT authentication?
Build your own token system.
Need file uploads?
Create everything manually.
Need email functionality?
Develop your own email protocol.
Need database connectivity?
Build your own database driver.
Sounds crazy, right?
This is exactly why NPM exists.
NPM gives developers access to millions of reusable packages created by other developers.
Instead of reinventing the wheel, we can use trusted solutions and focus on solving business problems.
Without NPM, Node.js would never have become as popular as it is today.
What Is NPM?
NPM stands for:
Node Package Manager
However, modern developers usually think of NPM as three things:
1. A Website
A marketplace of packages.
2. A Registry
A huge database storing packages.
3. A Command Line Tool
Used to install and manage packages.
Think of it like Amazon for software.
Instead of buying physical products, developers download code packages.
Why NPM Was Created
Before package managers existed, sharing code was difficult.
Developers would:
- Copy files manually
- Download ZIP archives
- Email source code
- Store reusable code in random folders
This created many problems:
- Version conflicts
- Missing files
- Difficult updates
- Dependency chaos
NPM solved these issues by creating a centralized ecosystem.
What Is a Package?
A package is simply reusable code.
Examples:
Express
Creates APIs and web servers.
Mongoose
Connects to MongoDB.
Axios
Makes HTTP requests.
JWT
Creates authentication tokens.
Nodemailer
Sends emails.
Instead of writing thousands of lines yourself:
npm install express
and much of the work is already done.
The Scale of NPM
NPM is considered one of the largest software registries in the world.
Millions of packages exist.
Every day:
- Developers publish packages
- Developers update packages
- Companies maintain packages
- Applications install packages
Most modern Node.js applications depend on dozens or even hundreds of packages.
First Look at NPM
Check NPM version:
npm -v
Example output:
11.0.0
Check Node version:
node -v
Output:
v24.0.0
Creating a Node.js Project
Suppose we start building AQAD Backend.
Create project folder:
mkdir aqad-backend
Move into folder:
cd aqad-backend
Initialize NPM:
npm init
NPM asks questions.
Example:
package name:
version:
description:
author:
license:
After completion:
package.json
is created.
Understanding package.json
This is one of the most important files in any Node.js project.
Think of package.json as:
The Identity Card of Your Application
It contains:
- Project name
- Version
- Dependencies
- Scripts
- Author information
- Project configuration
Example:
{
"name": "aqad-backend",
"version": "1.0.0",
"description": "AQAD Marketplace API",
"main": "app.js"
}
Why package.json Matters
Imagine a new developer joins AQAD.
Without package.json:
Nobody knows:
- Which packages are required
- Which versions are needed
- How to start the project
With package.json:
Everything is documented.
Quick Initialization
Instead of answering questions:
npm init -y
Automatically generates:
{
"name": "project",
"version": "1.0.0"
}
Most developers use this approach.
Installing Packages
Let's install Express.
npm install express
or
npm i express
NPM downloads package files.
After installation:
node_modules
package.json
package-lock.json
appear.
Understanding node_modules
Many beginners panic when they see:
node_modules
because it becomes huge.
Sometimes:
500 MB
1000 MB
2000 MB
Why?
Because packages have dependencies.
Those dependencies have dependencies.
Those dependencies have dependencies.
It creates a dependency tree.
Real-Life Analogy
Suppose AQAD buys products from a vendor.
That vendor depends on:
- Manufacturers
Manufacturers depend on:
- Raw material suppliers
Suppliers depend on:
- Transport companies
The chain grows larger and larger.
The same happens inside node_modules.
Example
Install:
npm install express
You think:
1 package
Reality:
Dozens of packages
because Express depends on many internal libraries.
Dependencies
A dependency is a package your application needs.
Example:
npm install express
package.json becomes:
{
"dependencies": {
"express": "^5.0.0"
}
}
This means:
The project requires Express.
Development Dependencies
Some packages are only needed during development.
Examples:
- Nodemon
- ESLint
- Prettier
- Jest
Install:
npm install nodemon --save-dev
or
npm i -D nodemon
package.json:
{
"devDependencies": {
"nodemon": "^3.0.0"
}
}
Dependencies vs Dev Dependencies
| Type | Used In Production | Used During Development |
|---|---|---|
| dependencies | Yes | Yes |
| devDependencies | No | Yes |
AQAD Example
Production dependency:
Express
JWT
AWS SDK
DynamoDB Client
Development dependency:
Nodemon
ESLint
Prettier
Understanding package-lock.json
After installing packages:
package-lock.json
appears automatically.
Many beginners delete it.
Don't.
What Does It Do?
Imagine AQAD orders:
100 Coca-Cola Bottles
Next month:
Vendor sends a slightly different version.
Now inventory behaves differently.
Problems begin.
Similarly:
Package versions can change.
package-lock.json locks exact versions.
This ensures:
Developer A
Developer B
Production Server
all use identical package versions.
Example
package.json:
{
"express": "^5.0.0"
}
The caret:
^
allows minor updates.
package-lock.json records the exact version installed.
Semantic Versioning (SemVer)
Almost every package follows:
MAJOR.MINOR.PATCH
Example:
2.4.7
Major Version
3.0.0
Breaking changes.
May require code modifications.
Minor Version
2.5.0
New features.
Usually backward compatible.
Patch Version
2.4.8
Bug fixes.
No major changes.
Example
1.0.0
↓
1.1.0
New feature added.
↓
1.1.1
Bug fixed.
↓
2.0.0
Breaking change introduced.
Understanding the Caret (^)
Example:
{
"express": "^5.0.0"
}
Means:
5.x.x
is acceptable.
But:
6.0.0
is not.
Understanding the Tilde (~)
Example:
{
"express": "~5.0.0"
}
Allows:
5.0.x
Only patch updates.
More restrictive.
Installing Specific Versions
Example:
npm install express@5.0.0
Useful in production systems.
Removing Packages
Remove package:
npm uninstall express
NPM updates:
package.json
package-lock.json
automatically.
Updating Packages
Check outdated packages:
npm outdated
Update packages:
npm update
NPM Scripts
One of the most powerful features.
Inside package.json:
{
"scripts": {
"start": "node app.js"
}
}
Run:
npm start
Instead of:
node app.js
AQAD Example
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest"
}
}
Commands:
npm run dev
npm test
Why Scripts Are Powerful
Every developer runs the same commands.
No confusion.
No documentation issues.
No manual setup.
Installing Packages Globally
Local install:
npm install nodemon
Only available in project.
Global install:
npm install -g nodemon
Available system-wide.
Publishing Packages
NPM isn't only for consuming packages.
You can publish your own.
Imagine AQAD builds:
aqad-payment-sdk
Other developers can install:
npm install aqad-payment-sdk
This is how the ecosystem grows.
Common Beginner Mistakes
Mistake 1
Committing node_modules
Bad:
Git Repository
└── node_modules
Huge repository.
Never do this.
Always add:
node_modules
to:
.gitignore
Mistake 2
Deleting package-lock.json
Many beginners remove it.
Keep it.
It ensures consistent installations.
Mistake 3
Installing Everything Globally
Bad habit.
Most packages should remain project-specific.
Mistake 4
Ignoring Version Changes
Updating blindly can break applications.
Always review major version upgrades.
Mini Exercises
Exercise 1
Create a new project.
Run:
npm init -y
Inspect package.json.
Exercise 2
Install:
npm install express
Observe:
node_modules
package.json
package-lock.json
Exercise 3
Install:
npm install nodemon --save-dev
Check devDependencies.
Exercise 4
Create a script:
"dev": "node app.js"
Run:
npm run dev
Try It Yourself
Create:
aqad-backend
Initialize:
npm init -y
Install:
npm install express
Install development tools:
npm install nodemon --save-dev
Add scripts:
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
}
}
Run:
npm run dev
Observe how NPM simplifies project management.
Real AQAD Example
A production AQAD backend may have:
Express
JWT
AWS SDK
Multer
Bcrypt
Helmet
Morgan
Joi
Winston
Nodemon
ESLint
Managing all of these manually would be impossible.
NPM becomes the warehouse manager of the entire software supply chain.

0 Comments