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?

Node.js is a JavaScript runtime environment. That single sentence carries three important words:

WordMeaning
JavaScriptThe programming language — same syntax you use in the browser
RuntimeAn environment that provides everything needed to execute code (memory management, I/O access, APIs)
EnvironmentNode.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:

ComponentPurpose
V8 JavaScript engineGoogle's blazing-fast JS engine from Chrome
libuvA C library for asynchronous I/O (file system, network, timers)
Event loopA 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

YearEvent
2009Ryan Dahl creates Node.js, presents at JSConf EU — standing ovation
2009npm conceived by Isaac Z. Schlueter
2010npm launches; Express.js released by TJ Holowaychuk
2011Windows support added; large companies begin adoption
2014io.js fork — community wanted faster releases and better governance
2015io.js merges back; Node.js Foundation formed under Linux Foundation
2019OpenJS Foundation formed (merger of Node.js Foundation + JS Foundation)
2023Node.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.

FeatureBrowser JavaScriptNode.js JavaScript
DOM manipulationdocument.getElementById()Not available (no DOM)
Window objectwindow, alert(), prompt()Not available
Global objectwindow (or globalThis)global (or globalThis)
File systemNot available (security sandbox)fs module — full disk access
HTTP serverNot available (client only)http module — create servers
OS infoLimited (navigator.userAgent)os module — full OS details
Process controlNot availableprocess object — env vars, exit, args
Binary dataArrayBuffer, BlobBuffer (plus ArrayBuffer)
Module systemES Modules (import/export)CommonJS (require/module.exports) + ES Modules
Networkfetch(), XMLHttpRequesthttp, https, net, dgram modules
TimerssetTimeout, setIntervalSame + setImmediate()
Consoleconsole.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

StepProcess
1. ParseReads your JavaScript source code and creates an AST (Abstract Syntax Tree)
2. IgnitionThe interpreter converts AST into bytecode and starts executing immediately
3. TurboFanThe optimizing compiler identifies "hot" (frequently run) code and compiles it to optimized machine code
4. DeoptimizeIf 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:

FeatureWhat It Does
Event loopThe core loop that orchestrates async operations
Thread pool4 threads (default) for operations the OS cannot do asynchronously (file I/O, DNS, etc.)
Async I/OUses OS-specific mechanisms: epoll (Linux), kqueue (macOS), IOCP (Windows)
TimersImplements setTimeout, setInterval, setImmediate
Child processesSpawning and managing subprocesses
Signal handlingListening for OS signals (SIGINT, SIGTERM, etc.)
File system operationsAsync and sync file I/O
DNS resolutionAsync 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

PillarWritten InRole
V8C++Compiles and executes JavaScript at near-native speed
libuvCProvides async I/O, event loop, thread pool, OS abstraction
Node.js APIsC++ / JSBuilt-in modules (fs, http, path, crypto, stream, etc.)

9. Use Cases — Where Node.js Shines

Ideal use cases

Use CaseWhy Node.js?Examples
REST APIsNon-blocking I/O handles many concurrent requests efficientlyExpress, Fastify, Nest.js APIs
Real-time applicationsEvent-driven model perfect for WebSocket connectionsChat apps, live dashboards, collaborative editors
MicroservicesLightweight, fast startup, low memory footprintNetflix, PayPal, Uber backend services
CLI toolsnpm ecosystem, fast execution, cross-platformwebpack, ESLint, Prettier, npm itself
StreamingBuilt-in Stream API for processing data in chunksVideo transcoding, log processing, file uploads
Server-Side RenderingSame language for frontend and backendNext.js, Nuxt.js, Remix
IoTLow resource usage, event-driven fits sensor dataHome automation, industrial monitoring
GraphQL serversAsync resolvers match the non-blocking modelApollo Server, Yoga

Where Node.js is NOT ideal

Use CaseWhy Not?Better Alternative
CPU-heavy computationBlocks the single thread (no async benefit)Python (NumPy), Go, Rust, C++
Machine learningV8 not optimized for matrix operationsPython (TensorFlow, PyTorch)
Heavy image/video processingCPU-bound; blocks event loopGo, Rust, or offload to a worker/service
Desktop GUI appsNot 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

MisconceptionReality
"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

  1. Node.js is a runtime, not a language or framework — it lets JavaScript run outside the browser by combining V8 + libuv + Node APIs.
  2. Ryan Dahl created Node.js in 2009 to solve the problem of blocking I/O in traditional thread-per-request servers.
  3. Non-blocking I/O means Node.js does not wait for file reads, database queries, or network calls — it delegates them and continues executing.
  4. The event loop is the core mechanism — it runs on a single thread, picks up I/O results from callback queues, and processes them.
  5. V8 compiles JavaScript to machine code using JIT compilation — making Node.js extremely fast for a dynamic language.
  6. 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.
  7. Node.js shines for REST APIs, real-time apps, microservices, CLI tools, and streaming — but struggles with CPU-intensive computation.
  8. 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