Episode 3 — NodeJS MongoDB Backend Architecture / 3.1 — Starting with NodeJS
3.1.a — Introduction to Node.js
In one sentence: Node.js is an open-source, cross-platform JavaScript runtime built on Chrome's V8 engine that uses an event-driven, non-blocking I/O model to build fast, scalable network applications.
Navigation: ← 3.1 Overview · Next → 3.1.b Setting Up Tools
Table of Contents
- 1. What Is Node.js?
- 2. History — Why Node.js Was Created
- 3. Node.js vs Browser JavaScript
- 4. Event-Driven, Non-Blocking I/O Model
- 5. Single-Threaded but Concurrent
- 6. The V8 Engine
- 7. libuv — The Async I/O Powerhouse
- 8. Node.js Architecture Diagram
- 9. Use Cases — Where Node.js Shines
- 10. Common Misconceptions
- Key Takeaways
- Explain-It Challenge
1. What Is Node.js?
Node.js is a JavaScript runtime environment. That single sentence carries three important words:
| Word | Meaning |
|---|---|
| JavaScript | The programming language — same syntax you use in the browser |
| Runtime | An environment that provides everything needed to execute code (memory management, I/O access, APIs) |
| Environment | Node.js adds server-side capabilities that browsers intentionally block (file system, network sockets, OS access) |
What Node.js is NOT
┌─────────────────────────────────────────────────────────────────┐
│ CLEARING UP CONFUSION │
│ │
│ Node.js is NOT a programming language. │
│ → The language is JavaScript (or TypeScript compiled to JS). │
│ │
│ Node.js is NOT a framework. │
│ → Express, Fastify, Nest.js are frameworks. │
│ → Node.js is what they run ON. │
│ │
│ Node.js is NOT a library. │
│ → It's a full runtime — like the JVM is for Java. │
│ │
│ Analogy: │
│ → JavaScript is the language (like English) │
│ → Node.js is the environment (like a stage for a speech) │
│ → Express is a tool/framework (like a microphone) │
└─────────────────────────────────────────────────────────────────┘
A Simple Definition
// This code runs in Node.js — NOT in a browser
const fs = require('fs');
// Reading a file from disk — impossible in browser JavaScript
const content = fs.readFileSync('hello.txt', 'utf8');
console.log(content);
The browser would throw require is not defined because browsers do not have the fs module. Node.js gives JavaScript superpowers that browsers deliberately withhold for security.
2. History — Why Node.js Was Created
The Problem (2009)
In 2009, most web servers used thread-based concurrency:
Traditional Server (Apache with PHP):
──────────────────────────────────────
Request 1 ──► Thread 1 ──► Read DB ──► (BLOCKED — waiting) ──► Respond
Request 2 ──► Thread 2 ──► Read DB ──► (BLOCKED — waiting) ──► Respond
Request 3 ──► Thread 3 ──► Read file ──► (BLOCKED — waiting) ──► Respond
Request 4 ──► NO THREAD AVAILABLE ──► WAIT IN QUEUE
...
Request 10,000 ──► System runs out of threads/memory ──► CRASH
Each request blocked a thread while waiting for I/O (database queries, file reads, network calls). With thousands of concurrent connections, servers ran out of threads and memory.
Ryan Dahl's Solution
Ryan Dahl, a software engineer, presented Node.js at JSConf EU in November 2009. His key insight:
"Most of what web applications do is wait for I/O. Why waste a thread on waiting?"
He combined three things:
| Component | Purpose |
|---|---|
| V8 JavaScript engine | Google's blazing-fast JS engine from Chrome |
| libuv | A C library for asynchronous I/O (file system, network, timers) |
| Event loop | A single-threaded loop that delegates I/O and picks up results when ready |
Node.js Server:
───────────────
Request 1 ──► Event Loop ──► Start DB query (non-blocking) ──► move on
Request 2 ──► Event Loop ──► Start file read (non-blocking) ──► move on
Request 3 ──► Event Loop ──► Start API call (non-blocking) ──► move on
...
Request 10,000 ──► Event Loop ──► Still fine! Just callbacks in a queue.
(When DB query finishes) ──► callback fires ──► Respond to Request 1
(When file read finishes) ──► callback fires ──► Respond to Request 2
Key Milestones
| Year | Event |
|---|---|
| 2009 | Ryan Dahl creates Node.js, presents at JSConf EU — standing ovation |
| 2009 | npm conceived by Isaac Z. Schlueter |
| 2010 | npm launches; Express.js released by TJ Holowaychuk |
| 2011 | Windows support added; large companies begin adoption |
| 2014 | io.js fork — community wanted faster releases and better governance |
| 2015 | io.js merges back; Node.js Foundation formed under Linux Foundation |
| 2019 | OpenJS Foundation formed (merger of Node.js Foundation + JS Foundation) |
| 2023 | Node.js 20 LTS — built-in test runner, permission model |
3. Node.js vs Browser JavaScript
JavaScript in the browser and JavaScript in Node.js share the same language core (ECMAScript) but have very different global objects and APIs.
| Feature | Browser JavaScript | Node.js JavaScript |
|---|---|---|
| DOM manipulation | document.getElementById() | Not available (no DOM) |
| Window object | window, alert(), prompt() | Not available |
| Global object | window (or globalThis) | global (or globalThis) |
| File system | Not available (security sandbox) | fs module — full disk access |
| HTTP server | Not available (client only) | http module — create servers |
| OS info | Limited (navigator.userAgent) | os module — full OS details |
| Process control | Not available | process object — env vars, exit, args |
| Binary data | ArrayBuffer, Blob | Buffer (plus ArrayBuffer) |
| Module system | ES Modules (import/export) | CommonJS (require/module.exports) + ES Modules |
| Network | fetch(), XMLHttpRequest | http, https, net, dgram modules |
| Timers | setTimeout, setInterval | Same + setImmediate() |
| Console | console.log() etc. | Same (writes to stdout/stderr) |
Code comparison
// --- BROWSER JavaScript ---
// Access the DOM (only in browser)
document.querySelector('h1').textContent = 'Hello!';
// Use the window object
window.alert('This only works in a browser');
// Fetch data from an API
fetch('https://api.example.com/data')
.then(res => res.json())
.then(data => console.log(data));
// --- NODE.JS JavaScript ---
const fs = require('fs'); // File system — browser can't do this
const http = require('http'); // Create HTTP server — browser can't do this
const os = require('os'); // Operating system info
// Read a file from disk
const data = fs.readFileSync('config.json', 'utf8');
// Create a web server
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from Node.js server!');
}).listen(3000);
// Access OS information
console.log(`Platform: ${os.platform()}`); // darwin, linux, win32
console.log(`CPUs: ${os.cpus().length}`);
console.log(`Free Memory: ${os.freemem() / 1024 / 1024} MB`);
4. Event-Driven, Non-Blocking I/O Model
This is the core philosophy of Node.js and the single most important concept to understand.
Blocking vs Non-Blocking
// ===== BLOCKING (Synchronous) =====
// Each line waits for the previous to complete
const fs = require('fs');
console.log('Step 1: Start');
const data = fs.readFileSync('large-file.txt', 'utf8'); // BLOCKS here
console.log('Step 2: File read complete'); // Runs after file is read
console.log('Step 3: Done');
// Output:
// Step 1: Start
// Step 2: File read complete (after potentially long wait)
// Step 3: Done
// ===== NON-BLOCKING (Asynchronous) =====
// I/O is delegated; execution continues immediately
const fs = require('fs');
console.log('Step 1: Start');
fs.readFile('large-file.txt', 'utf8', (err, data) => {
console.log('Step 2: File read complete'); // Runs when file is ready
});
console.log('Step 3: Move on immediately');
// Output:
// Step 1: Start
// Step 3: Move on immediately (doesn't wait!)
// Step 2: File read complete (callback fires later)
The Event Loop — How It Works
The event loop is the mechanism that makes non-blocking I/O possible. Think of it as a traffic controller that manages work and callbacks.
┌──────────────────────────────────────────────────────┐
│ THE EVENT LOOP │
│ │
│ ┌─────────────┐ │
│ │ Call Stack │ ← Executes JavaScript code │
│ │ (single) │ one function at a time │
│ └─────┬───────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌──────────────────────────┐ │
│ │ Event Loop │ ◄──► │ Callback Queue │ │
│ │ (checks │ │ (completed I/O callbacks │ │
│ │ for work) │ │ waiting to run) │ │
│ └─────┬───────┘ └──────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ libuv Thread Pool (4 threads by default) │ │
│ │ + OS-level async operations (epoll/kqueue) │ │
│ │ │ │
│ │ Handles: file I/O, DNS lookup, compression │ │
│ │ OS async: TCP/UDP sockets, signals, pipes │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
Event Loop Phases (Simplified)
The event loop runs in phases, each with its own queue:
┌───────────────────────────┐
┌─►│ timers │ ← setTimeout, setInterval callbacks
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ pending callbacks │ ← I/O callbacks deferred from previous cycle
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ idle, prepare │ ← internal use only
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ poll │ ← retrieve new I/O events; execute I/O callbacks
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ check │ ← setImmediate() callbacks
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ close callbacks │ ← socket.on('close', ...)
│ └─────────────┬─────────────┘
└─────────────────┘
Real-World Analogy
Imagine a restaurant with ONE waiter (single thread):
BLOCKING approach (traditional server):
Waiter takes Order 1 → goes to kitchen → STANDS THERE waiting
→ food ready → delivers → NOW takes Order 2 → ...
(10 tables? 10 waiters needed!)
NON-BLOCKING approach (Node.js):
Waiter takes Order 1 → gives to kitchen → immediately takes Order 2
→ gives to kitchen → takes Order 3 → ...
→ Kitchen bell rings (callback) → picks up Order 1 → delivers
→ Kitchen bell rings → picks up Order 3 → delivers
(One waiter handles dozens of tables efficiently!)
5. Single-Threaded but Concurrent
Node.js runs your JavaScript on a single thread — the main thread. But it is NOT limited to one thing at a time.
How concurrency works
Your JavaScript Code → Runs on MAIN THREAD (single)
│
File system operations → Delegated to LIBUV THREAD POOL (4 threads)
DNS lookups → Delegated to LIBUV THREAD POOL
Compression (zlib) → Delegated to LIBUV THREAD POOL
Crypto operations → Delegated to LIBUV THREAD POOL
│
TCP/UDP connections → Delegated to OS KERNEL (epoll/kqueue/IOCP)
HTTP requests → Delegated to OS KERNEL
Timers → Managed by LIBUV
Proving it with code
const crypto = require('crypto');
console.time('hash-1');
console.time('hash-2');
console.time('hash-3');
console.time('hash-4');
console.time('hash-5');
// These 4 run in parallel (libuv thread pool default = 4 threads)
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', () => {
console.timeEnd('hash-1'); // ~1 second
});
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', () => {
console.timeEnd('hash-2'); // ~1 second (parallel with hash-1)
});
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', () => {
console.timeEnd('hash-3'); // ~1 second (parallel)
});
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', () => {
console.timeEnd('hash-4'); // ~1 second (parallel)
});
// This 5th one WAITS for a thread to free up (only 4 in pool)
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', () => {
console.timeEnd('hash-5'); // ~2 seconds (waited for a free thread)
});
Increasing the thread pool
# Default is 4 — you can increase it
UV_THREADPOOL_SIZE=8 node app.js
6. The V8 Engine
V8 is Google's open-source JavaScript and WebAssembly engine, written in C++. It powers both Chrome and Node.js.
What V8 does
| Step | Process |
|---|---|
| 1. Parse | Reads your JavaScript source code and creates an AST (Abstract Syntax Tree) |
| 2. Ignition | The interpreter converts AST into bytecode and starts executing immediately |
| 3. TurboFan | The optimizing compiler identifies "hot" (frequently run) code and compiles it to optimized machine code |
| 4. Deoptimize | If assumptions break (e.g., a variable changed type), it falls back to bytecode |
JIT (Just-In-Time) Compilation
┌──────────────────────────────────────────────────────────┐
│ V8 JIT COMPILATION PIPELINE │
│ │
│ Source Code (.js) │
│ │ │
│ ▼ │
│ ┌─────────┐ Tokens ┌─────────┐ AST │
│ │ Scanner │ ──────────► │ Parser │ ──────────► │
│ └─────────┘ └─────────┘ │
│ │ │
│ ▼ │
│ ┌────────────┐ │
│ │ Ignition │ │
│ │ (Interpreter)│ │
│ └──────┬─────┘ │
│ │ Bytecode │
│ ┌───────────┤ │
│ │ │ │
│ Runs immediately Profiler watches │
│ │ which code is "hot" │
│ │ │ │
│ │ ┌─────▼──────┐ │
│ │ │ TurboFan │ │
│ │ │ (Optimizer) │ │
│ │ └─────┬──────┘ │
│ │ │ │
│ │ Optimized Machine │
│ │ Code │
│ │ │ │
│ ▼ ▼ │
│ EXECUTION (fast!) │
└──────────────────────────────────────────────────────────┘
Why V8 is fast
- JIT compilation — does not interpret line by line; compiles hot paths to native machine code
- Hidden classes — optimizes property access on objects (like C++ structs under the hood)
- Inline caching — remembers where to find properties to avoid repeated lookups
- Garbage collection — generational GC (young generation + old generation) minimizes pauses
- Written in C++ — low-level performance for the engine itself
7. libuv — The Async I/O Powerhouse
libuv is a C library originally written for Node.js that provides:
| Feature | What It Does |
|---|---|
| Event loop | The core loop that orchestrates async operations |
| Thread pool | 4 threads (default) for operations the OS cannot do asynchronously (file I/O, DNS, etc.) |
| Async I/O | Uses OS-specific mechanisms: epoll (Linux), kqueue (macOS), IOCP (Windows) |
| Timers | Implements setTimeout, setInterval, setImmediate |
| Child processes | Spawning and managing subprocesses |
| Signal handling | Listening for OS signals (SIGINT, SIGTERM, etc.) |
| File system operations | Async and sync file I/O |
| DNS resolution | Async DNS lookups (uses thread pool) |
What libuv delegates to the OS vs thread pool
┌───────────────────────────────────────────────────────┐
│ OS KERNEL (truly async — no threads) │
│ │
│ • TCP/UDP sockets (connecting, reading, writing) │
│ • Pipes │
│ • Signals │
│ • Timer events │
│ • Child process management │
└───────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────┐
│ LIBUV THREAD POOL (uses threads) │
│ Default: 4 threads (UV_THREADPOOL_SIZE) │
│ │
│ • File system operations (open, read, write, stat) │
│ • DNS lookups (getaddrinfo, getnameinfo) │
│ • Compression (zlib) │
│ • Crypto operations (pbkdf2, randomBytes, scrypt) │
│ • Any custom C++ addons using uv_queue_work │
└───────────────────────────────────────────────────────┘
8. Node.js Architecture Diagram
┌───────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION │
│ (JavaScript / TypeScript) │
│ │
│ const http = require('http'); │
│ const fs = require('fs'); │
│ const crypto = require('crypto'); │
└───────────────────────┬───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ NODE.JS BINDINGS │
│ (C++ bridge between JS and C) │
│ │
│ Translates JavaScript calls into C/C++ operations │
│ e.g., fs.readFile() → uv_fs_read() in libuv │
└──────────┬────────────────────────────────┬───────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────────────┐
│ V8 ENGINE │ │ libuv │
│ (Google, C++) │ │ (C library) │
│ │ │ │
│ • Parse JavaScript │ │ • Event loop │
│ • JIT compilation │ │ • Thread pool (4 default) │
│ • Memory management │ │ • Async I/O (OS delegation) │
│ • Garbage collection│ │ • Timers, signals, DNS │
│ • Execute machine │ │ • File system operations │
│ code │ │ • Child processes │
└──────────────────────┘ └──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ OPERATING SYSTEM │
│ │
│ epoll (Linux) │
│ kqueue (macOS/BSD) │
│ IOCP (Windows) │
│ │
│ Network, File System, Processes │
└──────────────────────────────────┘
The three pillars summarized
| Pillar | Written In | Role |
|---|---|---|
| V8 | C++ | Compiles and executes JavaScript at near-native speed |
| libuv | C | Provides async I/O, event loop, thread pool, OS abstraction |
| Node.js APIs | C++ / JS | Built-in modules (fs, http, path, crypto, stream, etc.) |
9. Use Cases — Where Node.js Shines
Ideal use cases
| Use Case | Why Node.js? | Examples |
|---|---|---|
| REST APIs | Non-blocking I/O handles many concurrent requests efficiently | Express, Fastify, Nest.js APIs |
| Real-time applications | Event-driven model perfect for WebSocket connections | Chat apps, live dashboards, collaborative editors |
| Microservices | Lightweight, fast startup, low memory footprint | Netflix, PayPal, Uber backend services |
| CLI tools | npm ecosystem, fast execution, cross-platform | webpack, ESLint, Prettier, npm itself |
| Streaming | Built-in Stream API for processing data in chunks | Video transcoding, log processing, file uploads |
| Server-Side Rendering | Same language for frontend and backend | Next.js, Nuxt.js, Remix |
| IoT | Low resource usage, event-driven fits sensor data | Home automation, industrial monitoring |
| GraphQL servers | Async resolvers match the non-blocking model | Apollo Server, Yoga |
Where Node.js is NOT ideal
| Use Case | Why Not? | Better Alternative |
|---|---|---|
| CPU-heavy computation | Blocks the single thread (no async benefit) | Python (NumPy), Go, Rust, C++ |
| Machine learning | V8 not optimized for matrix operations | Python (TensorFlow, PyTorch) |
| Heavy image/video processing | CPU-bound; blocks event loop | Go, Rust, or offload to a worker/service |
| Desktop GUI apps | Not designed for it (Electron exists but is resource-heavy) | Swift, Kotlin, C# |
Companies using Node.js in production
Netflix — Reduced startup time by 70% after switching from Java
PayPal — Built apps 2x faster, 33% fewer lines of code
LinkedIn — Moved from Ruby; 10x performance improvement on mobile
Uber — Handles millions of concurrent connections for ride matching
NASA — Uses Node.js for spaceflight data APIs
Walmart — Handles 500M+ page views on Black Friday with Node.js
Trello — Real-time board updates via WebSockets
10. Common Misconceptions
| Misconception | Reality |
|---|---|
| "Node.js is single-threaded, so it's slow" | It's single-threaded for your JS code — I/O runs on multiple threads/OS async. This makes it very fast for I/O-heavy workloads. |
| "Node.js can't use multiple cores" | Use the cluster module or worker_threads to utilize all CPU cores |
| "Node.js is only for small apps" | Netflix, PayPal, LinkedIn, Uber prove it scales to millions of users |
| "Node.js is the same as JavaScript" | JavaScript is the language; Node.js is the runtime (like JVM is to Java) |
| "npm and Node.js are the same thing" | npm is a package manager that comes bundled with Node.js, but they are separate projects |
| "Node.js replaces Python/Java/Go" | Each language has strengths; Node.js excels at I/O-bound work, not CPU-bound |
| "Callbacks mean Node.js is old/bad" | Modern Node.js uses Promises and async/await — callbacks are foundational but rarely written raw today |
Key Takeaways
- Node.js is a runtime, not a language or framework — it lets JavaScript run outside the browser by combining V8 + libuv + Node APIs.
- Ryan Dahl created Node.js in 2009 to solve the problem of blocking I/O in traditional thread-per-request servers.
- Non-blocking I/O means Node.js does not wait for file reads, database queries, or network calls — it delegates them and continues executing.
- The event loop is the core mechanism — it runs on a single thread, picks up I/O results from callback queues, and processes them.
- V8 compiles JavaScript to machine code using JIT compilation — making Node.js extremely fast for a dynamic language.
- libuv handles all the async I/O under the hood — using a thread pool for file/DNS operations and OS-level async for network operations.
- Node.js shines for REST APIs, real-time apps, microservices, CLI tools, and streaming — but struggles with CPU-intensive computation.
- Single-threaded does not mean single-operation — concurrency is achieved through the event loop, thread pool, and OS kernel.
Explain-It Challenge
Can you explain to a friend: "What is Node.js and how does it handle thousands of connections with just one thread?" If you can do it in under 90 seconds using the restaurant waiter analogy, you have mastered this topic.
Navigation: ← 3.1 Overview · Next → 3.1.b Setting Up Tools and Environment