The Secret Behind Handling Thousands of Requests
The Question Every Backend Developer Eventually Asks
Imagine your AQAD marketplace goes live.
Suddenly:
5,000 Retailers
2,000 Vendors
Hundreds of Orders Per Minute
Thousands of Product Searches
are happening simultaneously.
Yet your Node.js server continues running smoothly.
Naturally, someone asks:
"How can a single Node.js process handle so many requests?"
This question leads us to one of the most important concepts in backend development:
The Event Loop
Many developers hear this term.
Some memorize interview answers.
But very few truly understand what it does.
By the end of this chapter, you'll understand it well enough to explain it to another developer using nothing more than a restaurant story.
First, Let's Clear Up a Common Misunderstanding
Many beginners think:
Node.js = Multi-threaded Magic
Not exactly.
Your JavaScript code usually runs on:
One Main Thread
This surprises people.
Because they immediately ask:
"Then how does Node.js handle thousands of requests?"
The answer is:
It spends very little time waiting.
And the Event Loop plays a huge role in making that possible.
The Mega Restaurant Analogy
Imagine AQAD owns a massive restaurant.
Inside the restaurant:
One Head Waiter
Responsible for:
Taking Orders
Managing Requests
Coordinating Tasks
Multiple Kitchen Teams
Responsible for:
Cooking
Cleaning
Packaging
Deliveries
The head waiter never cooks.
The head waiter never washes dishes.
The head waiter delegates work.
This is how Node.js operates.
Mapping The Restaurant To Node.js
Restaurant:
Head Waiter
Node.js:
Event Loop
Restaurant:
Kitchen Staff
Node.js:
Operating System
libuv Thread Pool
Restaurant:
Customers
Node.js:
Incoming Requests
Restaurant:
Order Queue
Node.js:
Callback Queues
What Is The Event Loop?
Simple definition:
The Event Loop is a mechanism that continuously checks for tasks that are ready to execute and processes them when JavaScript is available.
That's the technical explanation.
Let's make it human.
Think:
Check Tasks
↓
Execute Task
↓
Check Again
↓
Execute Next Task
↓
Repeat Forever
That's essentially what the Event Loop does.
Why Node.js Needs The Event Loop
Imagine a retailer requests:
Product Catalog
Database query takes:
500ms
Should JavaScript stop everything and wait?
No.
Instead:
Send Query
Continue Working
When results arrive:
The Event Loop handles the callback.
This is why Node.js feels fast.
Visualizing Request Processing
Request arrives:
Get Products
Flow:
Request
↓
Event Loop
↓
Database Query
↓
Continue Processing Other Requests
↓
Database Response
↓
Callback Queue
↓
Event Loop Executes Callback
↓
Send Response
Notice:
Node.js isn't sitting idle.
It's constantly moving.
Understanding Event Loop Phases
Now we go one level deeper.
Many articles stop at the basic explanation.
Professional backend developers should understand the phases.
Imagine our restaurant has multiple workstations.
Each station handles specific tasks.
Node.js Event Loop behaves similarly.
High-Level Event Loop Flow
Timers
↓
Pending Callbacks
↓
Idle / Prepare
↓
Poll
↓
Check
↓
Close Callbacks
Then:
Repeat
Repeat
Repeat
This cycle runs continuously.
Let's simplify each phase.
Phase 1: Timers
Handles:
setTimeout()
setInterval()
Example:
setTimeout(() => {
console.log("Timer");
}, 1000);
After the timer expires:
The callback becomes eligible for execution.
Notice:
Eligible
does not mean:
Execute Immediately
This distinction matters.
Restaurant Analogy
Customer says:
Bring dessert in 10 minutes.
The kitchen sets a timer.
After 10 minutes:
The dessert becomes ready.
The waiter still needs to serve it.
Timers work similarly.
Phase 2: Pending Callbacks
Handles certain system-level callbacks.
Most beginners don't interact with this phase directly.
Think of it as:
Special Internal Tasks
For now, simply know it exists.
Phase 3: Idle / Prepare
Internal Node.js preparation phase.
Developers rarely interact with it.
Node.js performs housekeeping tasks here.
Think:
Restaurant Preparation Area
Phase 4: Poll Phase
This is the heart of the Event Loop.
Most real work happens here.
Examples:
Database Responses
File Reads
Network Requests
API Responses
The Poll phase waits for completed I/O operations.
AQAD Example
Retailer searches:
Soft Drinks
Node.js sends query:
Database Search
When results arrive:
The Poll phase receives them.
Then callbacks become available.
Why Poll Phase Is Important
Many Node.js applications spend most of their life here.
Because backend applications constantly perform:
Database Access
API Calls
File Operations
Network Requests
All of those eventually pass through the Poll phase.
Phase 5: Check Phase
This phase handles:
setImmediate()
Example:
setImmediate(() => {
console.log("Immediate");
});
The callback executes during the Check phase.
Phase 6: Close Callbacks
Handles cleanup operations.
Example:
Socket Closed
Connection Closed
Resource Cleanup
Think:
Restaurant Closing Procedures
The Event Loop Never Stops
Visualize:
Timers
↓
Pending
↓
Poll
↓
Check
↓
Close
↓
Repeat Forever
This cycle continues until the application exits.
Understanding setTimeout()
Consider:
console.log("Start");
setTimeout(() => {
console.log("Timer");
}, 0);
console.log("End");
Many beginners expect:
Start
Timer
End
Actual output:
Start
End
Timer
Why?
Because:
The timer callback must still travel through the Event Loop.
Understanding setImmediate()
Example:
setImmediate(() => {
console.log("Immediate");
});
This callback executes during the Check phase.
Interviewers often compare:
setTimeout()
vs
setImmediate()
We'll discuss the differences later in more detail.
Understanding process.nextTick()
Now we meet a special tool.
Example:
process.nextTick(() => {
console.log("Next Tick");
});
This behaves differently.
It executes before the Event Loop continues.
Think:
Urgent VIP Task
The restaurant handles it immediately.
Restaurant Analogy For nextTick
Imagine:
A VIP customer arrives.
Manager says:
Handle this before everything else.
That's similar to:
process.nextTick()
Why Node.js Handles Thousands of Requests
Now we can answer the original question.
Node.js succeeds because:
It Delegates Work
Database
Files
Networking
It Doesn't Wait
Continue Processing Requests
Event Loop Coordinates Everything
Ready Tasks
↓
Execute
↓
Repeat
This model is incredibly efficient.
Real AQAD Scenario
Suppose:
1,000 Retailers Searching Products
500 Vendors Updating Inventory
300 Orders Being Created
Node.js doesn't create thousands of waiting threads.
Instead:
Delegate
Queue
Execute When Ready
This saves memory and improves scalability.
Try It Yourself
Run:
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
console.log("3");
Predict the result before executing.
Answer:
1
3
2
Understanding why is more important than memorizing it.
Mini Exercise
Question 1
What is the primary responsibility of the Event Loop?
Question 2
Which phase handles timers?
Question 3
Which phase handles most I/O operations?
Question 4
What does setImmediate() use?
Question 5
Why can Node.js handle many requests efficiently?
Common Beginner Mistakes
Mistake 1
Thinking Node.js executes everything immediately.
Reality:
Callbacks must pass through Event Loop phases.
Mistake 2
Thinking setTimeout(0) means instant execution.
It means:
Execute when possible
Not:
Execute now
Mistake 3
Ignoring asynchronous behavior.
Understanding:
Event Loop
Promises
Async Await
is critical for backend development.
Mistake 4
Memorizing instead of understanding.
Focus on:
Delegation
Queues
Execution Flow
The phases become much easier afterward.
FAQ
Is the Event Loop part of JavaScript?
No.
It's provided by Node.js and supported by libuv.
Does Node.js use threads?
Yes.
But not in the way many beginners imagine.
Node.js uses background threads internally through libuv.
Is the Event Loop always running?
Yes.
As long as the Node.js process is active.
Why is Node.js good for APIs?
Because APIs spend a lot of time waiting for I/O operations.
Node.js handles waiting efficiently.
Global Objects in Node.js
Introduction: The Tools Available Everywhere in Your Backend
Imagine you have just joined a large restaurant as a manager.
On your first day, nobody gives you a box full of tools.
Instead, certain things are already available everywhere in the restaurant:
- A wall clock
- Employee attendance records
- Kitchen status board
- Emergency contact list
- Building address
- Daily sales report
No matter which room you enter, these resources are already there.
You don't need to request them.
You don't need to import them.
You don't need special permission to access them.
They are globally available.
Node.js works in a very similar way.
When your application starts, Node.js automatically provides several useful objects and variables that can be accessed from almost anywhere inside your application.
These are called Global Objects.
They help developers:
- Access system information
- Read environment variables
- Get application paths
- Handle command-line arguments
- Control application execution
- Monitor memory usage
- Manage process information
Almost every production backend uses these global objects daily.
Whether you are building:
- An e-commerce website
- A banking application
- A social media platform
- An inventory management system
- The AQAD marketplace
you will use global objects constantly.
Before building APIs and backend services, understanding these objects is essential.
What Are Global Objects?
A global object is an object that Node.js automatically makes available to your application.
You can use it directly without importing anything.
Example:
console.log("Hello World");
Notice something?
We never imported console.
Yet it works.
Why?
Because console is globally available.
Similarly:
setTimeout(() => {
console.log("Executed");
}, 1000);
Again:
- No import
- No require
Because setTimeout() is also globally available.
Node.js provides many such objects.
Why Global Objects Exist
Imagine if every file needed this:
const console = require('console');
const process = require('process');
const setTimeout = require('timers');
Your code would become unnecessarily complicated.
Node.js provides commonly used utilities globally to improve developer productivity.
Benefits:
Simplicity
Less boilerplate code.
Faster Development
Frequently used features are immediately available.
Better Developer Experience
Developers can focus on building applications rather than importing basic utilities.
Most Important Global Objects
As a backend developer you will frequently use:
| Global Object | Purpose |
|---|---|
| global | Global namespace |
| process | Current running process |
| __dirname | Current directory |
| __filename | Current file |
| console | Logging |
| Buffer | Binary data |
| setTimeout | Delayed execution |
| setInterval | Repeated execution |
Understanding the global Object
The global object is the highest-level object in Node.js.
Think of it as the main control room of a large company.
Everything that is globally accessible is attached to this object.
Example:
console.log(global);
This prints the Node.js global object.
Creating Global Variables
Example:
global.companyName = "AQAD";
console.log(global.companyName);
Output:
AQAD
You can access it from any file.
Why Global Variables Are Dangerous
Beginners often think:
"Great! I'll store everything globally."
Bad idea.
Imagine 50 employees editing the same whiteboard simultaneously.
Chaos.
Similarly:
global.user = {
name: "John"
};
Another file:
global.user = {
name: "Ahmed"
};
Now data becomes unpredictable.
Production applications avoid excessive global variables.
The process Object
Among all global objects, process is one of the most important.
It represents the currently running Node.js application.
Think of it as the manager responsible for monitoring the restaurant.
The manager knows:
- Revenue
- Staff count
- Working hours
- Current status
- Resource usage
Similarly, process knows everything about the running application.
Getting Process Information
Example:
console.log(process);
This returns detailed information about the running application.
Process ID
Every running application gets an ID.
console.log(process.pid);
Output:
5231
This helps administrators identify running processes.
Node.js Version
console.log(process.version);
Output:
v24.x.x
Useful when debugging compatibility issues.
Current Platform
console.log(process.platform);
Output: win32 or linux or darwin
This helps create platform-specific logic.
Exiting the Application
Sometimes we want to stop the application.
Example:
process.exit();
Node.js immediately terminates.
AQAD Example
Suppose AQAD cannot connect to DynamoDB.
Instead of keeping a broken application alive:
if (!databaseConnected) {
console.log("Database unavailable");
process.exit(1);
}
This prevents further failures.
Environment Variables
One of the most important backend concepts.
Every professional Node.js application uses environment variables.
The Real-World Problem
Imagine storing AWS credentials directly inside code.
const accessKey = "ABC123";
const secretKey = "XYZ456";
Dangerous.
If code reaches GitHub:
- Credentials leak
- Database compromised
- Cloud resources exposed
Instead we use environment variables.
Accessing Environment Variables
Node.js provides:
process.env
Example:
console.log(process.env);
This prints all environment variables.
Reading Specific Variables
console.log(process.env.PORT);
Output:
5000
AQAD Example
Production configuration:
PORT=5000
AWS_REGION=me-central-1
DB_TABLE=products
JWT_SECRET=mySecretKey
Accessing them:
const PORT = process.env.PORT;
const REGION = process.env.AWS_REGION;
Why Environment Variables Matter
Benefits:
Security
Sensitive data stays outside code.
Flexibility
Different configurations for:
- Development
- Testing
- Production
Scalability
Easy deployment across servers.
Using dotenv
Most projects use:
npm install dotenv
Create:
PORT=5000
Then:
require('dotenv').config();
console.log(process.env.PORT);
Output:
5000
This is standard industry practice.
Understanding __dirname
One of the most used Node.js variables.
__dirname gives the directory of the current file.
Imagine you are inside a warehouse.
Someone asks:
"Which warehouse are you currently standing in?"
That's what __dirname tells Node.js.
Example:
console.log(__dirname);
Output:
C:\Projects\AQAD\controllers
Why __dirname Is Useful
Suppose you need to load a file.
const filePath = __dirname + "/data.json";
Node.js knows exactly where to look.
Without it, file paths can break.
AQAD Example
Product images:
uploads/
products/
vendors/
Saving image:
const path =
__dirname + "/uploads/products";
The backend always knows the correct location.
Understanding __filename
While __dirname gives the folder,
__filename gives the complete file path.
Example:
console.log(__filename);
Output:
C:\Projects\AQAD\controllers\userController.js
Difference Between __dirname and __filename
Example:
__dirname
Output:
C:\Projects\AQAD\controllers
Example:
__filename
Output:
C:\Projects\AQAD\controllers\userController.js
Think of it this way:
Directory = Building
Filename = Exact Room
Command-Line Arguments
Node.js allows passing values while starting the application.
Example:
node app.js hello
Access:
console.log(process.argv);
Output:
[
'node',
'app.js',
'hello'
]
AQAD Example
Suppose you want to run different scripts.
node app.js development
const mode = process.argv[2];
console.log(mode);
Output:
development
Useful for automation scripts.
Monitoring Memory Usage
Node.js provides:
process.memoryUsage();
Example:
console.log(process.memoryUsage());
Output:
{
heapUsed: 4500000,
heapTotal: 9000000
}
Why This Matters
Imagine AQAD suddenly becomes slow.
Thousands of retailers are placing orders.
You can monitor memory consumption:
console.log(
process.memoryUsage().heapUsed
);
This helps detect memory leaks.
Real Backend Example
Suppose AQAD API starts.
console.log("Server Starting...");
console.log("Directory:", __dirname);
console.log("Environment:", process.env.NODE_ENV);
console.log("Process ID:", process.pid);
Output:
Server Starting...
Directory: C:\AQAD
Environment: production
Process ID: 3412
This information is commonly logged in production systems.
Common Mistakes
Mistake 1
Hardcoding Secrets
Bad:
const jwtSecret = "123456";
Good:
const jwtSecret =
process.env.JWT_SECRET;
Mistake 2
Using Too Many Global Variables
Bad:
global.userData = {};
Avoid unnecessary global state.
Mistake 3
Committing .env Files
Bad:
git add .env
git commit
Always add:
.env
to:
.gitignore
Mistake 4
Using Relative Paths Incorrectly
Bad:
"./uploads/image.png"
Good:
__dirname + "/uploads/image.png"
Mini Exercises
Exercise 1
Print:
- Node version
- Platform
- Process ID
Exercise 2
Create:
APP_NAME=AQAD
Print it using:
process.env.APP_NAME
Exercise 3
Print:
__dirname
and
__filename
Observe the difference.
Exercise 4
Pass your name using command line:
node app.js Aqad
Print:
Hello Aqad
using:
process.argv
Try It Yourself
Create a file:
console.log("PID:", process.pid);
console.log("Node Version:",
process.version);
console.log("Directory:",
__dirname);
console.log("File:",
__filename);
console.log("Arguments:",
process.argv);
Run:
node app.js test
Analyze the output.
Try changing arguments and observe the results.
Understanding the Node.js Modules System
Introduction: Why One Huge File Becomes a Disaster
Imagine AQAD has become one of the largest B2B marketplaces in the UAE.
The platform now handles:
- 50,000+ retailers
- 10,000+ vendors
- Millions of products
- Thousands of daily orders
Now imagine the entire business running from a single room.
Inside that room:
- Customer support team
- Finance team
- Logistics team
- Vendor management team
- Marketing team
- HR team
Everyone is working in the same place.
Files are mixed.
Documents are scattered.
People interrupt each other.
Nobody knows where anything is.
Very quickly, the company becomes impossible to manage.
The same thing happens in software.
When beginners start learning Node.js, they often write everything inside one file:
// app.js
// Login Code
// Product Code
// Order Code
// Payment Code
// Delivery Code
// Inventory Code
// Notification Code
Initially this looks fine.
But after a few months:
- 5,000 lines
- 10,000 lines
- 20,000 lines
Now the file becomes a nightmare.
Finding bugs becomes difficult.
Adding features becomes risky.
Team collaboration becomes painful.
This is exactly why the Module System exists.
Modules allow us to divide large applications into smaller manageable pieces.
Think of modules as departments inside a company.
Each department has a specific responsibility.
Together they make the entire organization work efficiently.
What Is a Module?
A module is simply a separate file that contains related code.
For example:
AQAD
│
├── products.js
├── orders.js
├── payments.js
├── users.js
└── app.js
Instead of putting everything into one file, we divide responsibilities.
Real-Life Analogy
Consider a restaurant.
Would one employee handle:
- Taking orders
- Cooking food
- Managing inventory
- Handling payments
- Delivering food
No.
Different people perform different jobs.
Similarly:
User Module
Product Module
Order Module
Payment Module
Each module focuses on one responsibility.
Why Modules Are Important
Without modules:
// 15,000 lines in one file
With modules:
user.js
product.js
order.js
payment.js
inventory.js
Benefits:
Better Organization
Code is easier to understand.
Easier Maintenance
Finding bugs becomes faster.
Reusability
One module can be reused in multiple places.
Team Collaboration
Multiple developers can work simultaneously.
Scalability
Applications can grow without becoming messy.
Your First Module
Let's create a simple module.
Step 1: Create math.js
function add(a, b) {
return a + b;
}
Now the function exists only inside this file.
Other files cannot access it.
Exporting a Module
To share code, we must export it.
function add(a, b) {
return a + b;
}
module.exports = add;
Now the function becomes available outside the file.
Importing a Module
Inside app.js:
const add = require('./math');
console.log(add(10, 20));
Output:
30
Congratulations.
You have created your first Node.js module.
Understanding module.exports
This is one of the most important concepts in Node.js.
Think of a department manager.
The department does not expose everything.
Only selected resources are shared.
Similarly:
module.exports
controls what becomes accessible outside the file.
Example
const companyName = "AQAD";
function calculateRevenue() {}
function calculateProfit() {}
module.exports = {
calculateRevenue,
calculateProfit
};
Accessible:
calculateRevenue()
calculateProfit()
Not accessible:
companyName
unless exported.
Exporting Multiple Functions
Most real applications export multiple functions.
Example:
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = {
add,
subtract
};
Importing
const math = require('./math');
console.log(math.add(10, 5));
console.log(math.subtract(10, 5));
Output:
15
5
Destructuring Imports
A cleaner approach:
const {
add,
subtract
} = require('./math');
Now:
console.log(add(10, 5));
This style is common in production applications.
AQAD Example: Product Module
Imagine AQAD's product functionality.
product.js
function getProducts() {
return "Products List";
}
function addProduct() {
return "Product Added";
}
module.exports = {
getProducts,
addProduct
};
app.js
const {
getProducts,
addProduct
} = require('./product');
console.log(getProducts());
console.log(addProduct());
Output:
Products List
Product Added
How require() Works Internally
When Node.js encounters:
require('./product')
it performs several steps.
Step 1
Locate file.
product.js
Step 2
Read file content.
Step 3
Execute file.
Step 4
Store result in memory.
Step 5
Return exported value.
Think of it as:
Restaurant receives ingredient request.
- Find ingredient
- Fetch ingredient
- Prepare ingredient
- Store if needed
- Deliver ingredient
Module Caching
One of Node.js's smartest features.
Example
product.js
console.log("Module Loaded");
module.exports = {};
app.js
require('./product');
require('./product');
require('./product');
Output:
Module Loaded
Only once.
Why?
Node.js caches modules.
After first load:
Memory Cache
Future requests use cached version.
Benefits
Faster Performance
No repeated file reading.
Reduced CPU Usage
Less work for Node.js.
Better Scalability
Important for large applications.
Real AQAD Example
Suppose:
database.js
creates DynamoDB connection.
Without caching:
Every file would reconnect.
Bad.
With caching:
Single connection reused.
Much better.
Understanding require Path Types
Node.js supports different paths.
Relative Path
require('./product')
Current folder.
Parent Folder
require('../product')
One level above.
External Package
require('express')
Node.js searches inside:
node_modules
AQAD Folder Structure Example
aqad
│
├── app.js
│
├── controllers
│ ├── userController.js
│ └── orderController.js
│
├── services
│ ├── paymentService.js
│ └── emailService.js
│
├── models
│ ├── userModel.js
│ └── orderModel.js
Import example:
const userController =
require('./controllers/userController');
Creating Utility Modules
Utilities are reusable helper functions.
utils.js
function generateOrderId() {
return Math.random();
}
module.exports = {
generateOrderId
};
app.js
const {
generateOrderId
} = require('./utils');
console.log(generateOrderId());
Built-In Modules
Not all modules are created by you.
Node.js includes many built-in modules.
Examples:
| Module | Purpose |
|---|---|
| fs | File operations |
| path | Path management |
| os | System information |
| http | Web server |
| events | Event handling |
Example
const fs = require('fs');
Notice:
No path.
Why?
Because fs is built into Node.js.
Third-Party Modules
These come from NPM.
Example:
npm install express
Then:
const express =
require('express');
Express becomes available because it exists in:
node_modules
Types of Modules in Node.js
There are three major types.
1. Local Modules
Created by you.
Example:
require('./product')
2. Core Modules
Built into Node.js.
Example:
require('fs')
3. Third-Party Modules
Installed from NPM.
Example:
require('express')
Common Beginner Mistakes
Mistake 1
Forgetting Exports
Bad:
function add() {}
Trying:
require('./math')
Result:
undefined
Mistake 2
Wrong Path
Bad:
require('product')
Correct:
require('./product')
Mistake 3
Exporting Before Declaration
Bad:
module.exports = add;
const add = () => {};
Can cause issues.
Mistake 4
Creating Huge Modules
Bad:
user.js
(5000 lines)
Better:
userController.js
userService.js
userValidation.js
userRoutes.js
Mini Exercises
Exercise 1
Create:
calculator.js
Export:
add()
multiply()
Import them into:
app.js
Exercise 2
Create:
user.js
Export:
getUser()
createUser()
Call them from:
app.js
Exercise 3
Verify module caching.
Add:
console.log("Loaded");
inside module.
Require it three times.
Observe output.
Try It Yourself
Create:
project
│
├── app.js
├── products.js
├── orders.js
products.js
module.exports = {
getProducts() {
return "All Products";
}
};
orders.js
module.exports = {
getOrders() {
return "All Orders";
}
};
app.js
const products =
require('./products');
const orders =
require('./orders');
console.log(products.getProducts());
console.log(orders.getOrders());
Run:
node app.js
Observe how separate modules work together.
Real-World AQAD Project Structure
As AQAD grows, modules become critical.
Instead of:
app.js
(25,000 lines)
We organize:
controllers/
services/
models/
routes/
middlewares/
utils/
config/
Each folder becomes a department.
Each file becomes a specialist employee.
This architecture makes large applications manageable.

0 Comments