This episode digs into the history of Node.js's birth, the role of the V8 engine and the libuv library, and the event-driven architecture that lets a single thread serve thousands of connections. You also see the difference between blocking and non-blocking I/O directly.

Node.js was born out of frustration with the limitations of traditional web servers where one connection used one thread. In 2009, Ryan Dahl created Node.js as a server-side JavaScript runtime built on Google's V8 engine and the libuv library for asynchronous I/O.
This episode 1 builds the conceptual foundation that the whole series will use: what Node.js is, why the event-driven and non-blocking I/O model is its main differentiator, and how the event loop works behind the scenes. You won't write much code in this episode, but this understanding determines the quality of your code in the episodes ahead.
Node.js was first released in 2009, right after Ryan Dahl demonstrated a prototype that combined JavaScript V8 with a non-blocking event loop. Before that, JavaScript only lived in the browser. Node.js moved it to the server by leveraging two key technologies:
In 2010 came npm — a package registry that changed how developers share code. Today npm is the largest package ecosystem in the world, and Node.js is the foundation of many modern tools such as Next.js, Vite, and your entire frontend toolchain.
The key difference between release channels: the current version is only supported until the next release arrives, while LTS versions receive active support for years. Choose LTS for production servers and current only if you deliberately want to try the latest features.
Since 2015, Node.js has adopted scheduled releases with an LTS (Long-Term Support) channel that is stable for production. Versions 20, 22, and 24 are the latest line of LTS releases — with active security support for years, so backend teams can use Node.js with confidence.
node --version
node -p "process.release.lts"The command node -p "process.release.lts" prints the LTS codename. If it returns null, you're using a non-LTS release — fine for experiments, but for production choose an LTS version.
The classic web server model allocates one thread per connection. The more connections, the more memory and threads are consumed. Node.js takes a different approach: one main thread runs JavaScript, while slow I/O operations — file reads, network connections, database queries — are handed off to libuv and processed outside the main thread.
That's why Node.js is very efficient for I/O-bound workloads, such as APIs, proxies, and real-time messaging. The key term is non-blocking I/O, where code doesn't stop waiting for an operation to finish. Another term that often comes up is backpressure — when data is produced faster than it's consumed, data piles up and the flow must be slowed down. Node.js handles this through stream buffers, which we'll discuss in episode 5.
Conversely, CPU-bound workloads — hashing, compression, image processing — don't automatically benefit from this model. Such operations still run on the main thread and can stall everything; we'll discuss strategies to handle them in episode 19.
The event loop is the mechanism that processes operations in turns: take a callback from the queue, run it to completion, then move on to the next callback. A simple diagram looks like this:
operation complete -> callback enters queue -> event loop runs the callbackBecause only one thread runs JavaScript, blocking in one place stalls everything. This is the reason practices like fs.readFileSync are not recommended on production servers — episodes 5 and 19 will cover this more deeply.
The event loop runs in several phases that repeat continuously: timers, pending callbacks, poll, check, and close callbacks. setTimeout is processed in the timers phase, network I/O in the poll phase, and setImmediate in the check phase. You don't need to memorize all the phases now — what matters is understanding that operations are scheduled, not executed immediately — but we'll break down these phases together with profiling in episode 19.
Try comparing the two versions of the code below. The first version uses readFileSync, which blocks the main thread:
const fs = require("fs");
const data = fs.readFileSync("/tmp/contoh.txt", "utf8");
console.log(data);
console.log("Selesai setelah file dibaca");The second version uses readFile, which is non-blocking:
const fs = require("fs");
fs.readFile("/tmp/contoh.txt", "utf8", (err, data) => {
if (err) throw err;
console.log(data);
});
console.log("Selesai tanpa menunggu file dibaca");In the non-blocking version, the message Selesai tanpa menunggu appears first because the callback function is scheduled to the event loop. This pattern — passing a function as a callback — is a Node.js hallmark you'll encounter in almost every module. Callback within callback also gave birth to the term callback hell, and the solution is Promises, which you'll use starting from episode 4.
Also notice the shape of Node.js's built-in callback: the first argument is always err, and the following arguments are the operation's data. This is the error-first callback convention used by the entire asynchronous API in Node.js. With this convention, errors no longer depend on a regular try-catch, because an asynchronous operation completes after the main thread has already moved forward — it's the callback that receives the news of success or failure.
Node.js performance comes largely from V8. V8 compiles JavaScript directly to machine code using JIT (just-in-time compilation) techniques that are continuously optimized. V8 updates automatically ride along into Node.js with each release, so your application gets the latest optimizations without changing code.
The Node.js ecosystem wouldn't be this rich without npm. With a single command, you can add mature libraries like Express, Prisma, or a Redis client. We'll only start using it intensively in episode 2 onward, but it's time to understand npm's position as the center of package distribution.
npm records all dependencies in package.json and locks them in package-lock.json. This lock file is what makes npm ci work deterministically in episode 21, ensuring everyone — from a developer's laptop to the CI pipeline — installs exactly the same dependencies. Treating the lockfile as part of the codebase is a hallmark of a disciplined team.
Here's what to take away:
In the next episode, episode 2, we'll discuss installation, node, npm, and npx — how these three commands work, the structure of package.json, semver, and when to use npx to run tools without a global install. Make sure Node.js is installed, because we'll start typing a lot in the terminal.