Sitemap
Mi Do

Mi Do

You need any support - feel free to reach me out https://dorokhovich.com/

Press enter or click to view image in full size

Node.js: The Legacy Standard

8 min readMay 4, 2026

--

Origins & Purpose: The 2009 Revolution

To understand the architecture of Node.js, one must first look back at the state of the web in 2009. The internet was waking up from the era of static documents and transitioning into the dynamic, flickering screens of Web 2.0. Browsers were suddenly demanding live data, utilizing technologies like AJAX and long-polling to keep connections open. The web was becoming beautifully interactive, but behind the scenes, web servers were gasping for air. At the JSConf EU conference that year, a young developer named Ryan Dahl stepped onto the stage and presented a radical, seemingly absurd idea: taking JavaScript out of the browser and putting it directly on the server. Dahl recognized that JavaScript possessed a unique, overlooked superpower. Because it was designed to handle unpredictable user clicks in a browser, its functions were treated as first-class citizens, and it was inherently event-driven.

The problem Dahl sought to solve was infamously known as the C10k problem, a technical bottleneck where traditional servers choked when attempting to handle ten thousand concurrent connections. In the traditional web architecture dominated by Apache and PHP, servers operated on a “Thread-per-Request” model. To understand this, imagine a busy restaurant where the manager hires a brand-new waiter every time a single customer walks through the door. If a customer takes five minutes to look at the menu, that waiter simply stands at the table, staring blankly, doing absolutely nothing while drawing a full salary. Eventually, the kitchen becomes so crammed with idle waiters that no actual cooking can get done. Node.js introduced an entirely different paradigm through asynchronous, non-blocking I/O. In the Node.js restaurant, there is only one incredibly fast waiter. This single waiter takes an order, hands it to the kitchen, and immediately walks to the next table to take another order. When the kitchen finishes cooking the first meal, they ring a bell, and the waiter delivers it.

Stripping away the analogy, the traditional restaurant waiters are operating system threads. Every time a connection hit an Apache server, the operating system spawned a new thread, immediately allocating roughly two megabytes of RAM. Ten thousand connections meant twenty gigabytes of memory consumed just to keep the connections open, not to mention the immense CPU overhead required to constantly context-switch between thousands of threads. Dahl realized that the vast majority of web server execution time is not spent computing math, but rather waiting for the network, waiting for a database, or waiting for the file system. By utilizing an asynchronous, single-threaded event loop, Node.js sidestepped the heavy RAM allocation entirely. It relied on the operating system’s underlying notification subsystems, like epoll on Linux or kqueue on macOS, to simply alert the JavaScript runtime when a piece of data was finally ready. Node.js was not just a tool for writing backend JavaScript; it was a fundamental paradigm shift in how servers managed hardware resources.

Core Architecture: The Engine, The Muscle, and The Heartbeat

Underneath its sleek exterior, JavaScript is actually terrible at heavy lifting. It was never designed to read files from a hard drive or encrypt network packets. In the architecture of Node.js, JavaScript acts merely as an orchestrator, while the real heavy lifting is delegated to deeper, lower-level systems. The first crucial component of this architecture is the V8 Engine, the brains of the operation. Originally gifted to the open-source world by Google for the Chrome browser, V8 takes human-readable JavaScript code and translates it into raw machine code. It does this through Just-In-Time (JIT) compilation, watching the code as it runs and aggressively optimizing hot pathways. V8 also handles the intricate dance of memory management, utilizing a garbage collector that periodically sweeps through the heap to reclaim memory, pausing execution for mere fractions of a millisecond to keep the application lean.

However, V8 alone cannot interact with the operating system. When a Node.js developer commands the server to read a file or hash a password, V8 passes the instruction down to the muscle of the operation: libuv. Written entirely in C++, libuv is a multi-platform support library with a focus on asynchronous I/O. It completely shatters the widespread myth that Node.js is entirely single-threaded. While the JavaScript code you write executes on a single thread, libuv maintains a hidden pool of C++ worker threads in the background. When your JavaScript asks to read a massive video file from disk, libuv assigns that heavy task to one of its worker threads. The JavaScript thread immediately moves on to serve the next user. Once the C++ worker thread finishes reading the file, it hands the data back to the JavaScript realm.

The mechanism that seamlessly weaves the V8 engine and the libuv worker threads together is the Event Loop, the true heartbeat of Node.js. The Event Loop is not a magical entity; it is a meticulously structured C++ while-loop operating in six distinct phases. To trace the journey of an HTTP request, imagine the data arriving and entering the Poll phase, where Node.js spends the majority of its time waiting for incoming connections. If a database query is required, the task is handed to libuv, and the loop spins forward. Once the database responds, its callback is queued into the Pending Callbacks phase. Timers like setTimeout are evaluated in the Timers phase, while setImmediate instructions are uniquely executed in the Check phase. Finally, cleanup operations occur in the Close phase.

Get Mi Do’s stories in your inbox

Join Medium for free to get updates from this writer.

For the master developer, the real power — and danger — lies in the spaces between these phases, governed by Macrotasks and Microtasks. When a phase completes, Node.js pauses to completely drain the Microtask queue, which includes resolved Promises, before moving to the next phase. Even higher priority is the VIP lane of process.nextTick(), which executes before absolutely anything else, cutting the line ahead of even resolved Promises. This immense power comes with a critical warning. If a developer accidentally writes a massive synchronous while loop in their JavaScript, V8 will lock up, the Event Loop will freeze in the Poll phase, and the highly efficient single waiter will effectively drop dead on the restaurant floor, ignoring all other customers until the loop finishes.

The Module System Civil War

Because Node.js was a pioneer, it suffered the tragedy of being first. When Ryan Dahl created Node.js, the official JavaScript language specification lacked any native way to import or export files. Consequently, Node.js had to invent its own module system from scratch, giving birth to CommonJS. This system became the old guard of backend JavaScript, recognized instantly by its require() and module.exports syntax. CommonJS was designed strictly for the server, meaning it operates synchronously. When you require a file, the main thread halts, physically goes to the hard drive, reads the file, executes it, and caches the result in memory.

Under the hood, Node.js achieves this by secretly wrapping every CommonJS file in an invisible Immediately Invoked Function Expression (IIFE). Before V8 even sees your code, Node.js prepends a function signature to the top of your file, magically injecting variables like __dirname, __filename, module, and exports directly into your local scope. This synchronous, heavily wrapped architecture carried the entire NPM ecosystem on its back for a decade. But as the web evolved, the official JavaScript standard finally introduced its own module system: ES Modules (ESM). Driven by the import and export syntax, ESM was designed for the modern web. It parses files asynchronously, allows for top-level await to pause execution while fetching resources, and relies on strict static analysis. Because ESM statically analyzes imports before executing the code, modern bundlers can perform “tree-shaking,” intelligently discarding unused code to keep application sizes incredibly small.

Today, Node.js developers are trapped in a geopolitical drama, navigating a messy transitional era between these two systems. The Node ecosystem is fractured. To handle the interoperability nightmare, developers must manually define their allegiance using the type: “module” flag in their package.json, or by explicitly naming files with .cjs and .mjs extensions. Mixing the two is fraught with peril. Because CommonJS is synchronous and dynamic, you can usually import a CommonJS module into an ES Module by grabbing its default export. However, attempting the reverse — using a synchronous require() to pull in an asynchronous ES Module — results in a painful, immediate syntax error that has frustrated millions of developers. The civil war is slowly ending with ESM as the undeniable victor, but the scars of CommonJS will remain in legacy codebases for years to come.

Modern Node.js: Catching Up and Shedding Baggage

For a long time, Node.js reigned unchallenged. But absolute power leads to stagnation, and the runtime eventually became bloated, relying heavily on third-party libraries for basic functionality. This complacency birthed intense pressure from younger, faster, and leaner rivals like Deno and Bun, which offered incredible developer experiences straight out of the box. Faced with an existential threat, the aging king has awakened. Node.js is currently undergoing a massive renaissance, internalizing features that previously required heavy, deeply nested NPM packages, and transforming into an “all batteries included” runtime.

The most prominent shift is Node’s aggressive integration of native web standards, driven by the WinterCG initiative to make JavaScript runtimes behave identically to the browser. For years, making a simple HTTP request in Node meant downloading bloated packages like axios or node-fetch. Today, Node.js features a native fetch API, fully integrated Web Streams, and standardized Web Crypto APIs. By adopting these standards, Node replaces thousands of lines of community-written JavaScript with highly optimized, C++ backed native implementations, resulting in vastly superior performance and a dramatically lighter node_modules folder.

This modernization extends deeply into the out-of-the-box developer experience. The ubiquitous dotenv package is being retired in favor of Node’s native env-file execution flag. The need for third-party file watchers like nodemon has been eradicated by the built-in watch command. Even testing frameworks like Jest and Mocha are facing stiff competition from the newly integrated node:test runner, which provides robust assertions, mocking, and coverage reports natively. Node is actively shedding its historical baggage, choosing to give developers exactly what they need the moment they install the runtime.

For the enterprise master, this evolution also brings a hardened security posture and revolutionary deployment strategies. Recognizing the danger of malicious NPM packages stealing environment variables or encrypting file systems, modern Node.js has introduced an experimental permissions model. Developers can now execute a script with strict flags that explicitly deny network access or restrict file system reads to specific directories, moving the runtime toward a zero-trust architecture. Furthermore, the introduction of Single Executable Applications (SEA) allows developers to inject their entire JavaScript codebase into a pre-compiled Node binary. This decoupling means you can ship a single, standalone executable file to a client’s server, entirely removing the need for them to install Node.js or run an NPM install. By acknowledging its flaws and embracing the future, Node.js proves that it is not a dying legacy technology, but an adaptable, battle-tested standard ready to power the next decade of the web.

--

--

Mi Do
Mi Do

Written by Mi Do

You need any support - feel free to reach me out https://dorokhovich.com/