---
title: "01 — What Node.js Actually Is — JavaScript Without a Browser"
uid: introduction-nodejs
tags: ["v8", "nodejs", "roadmap:nodejs", "fundamentals", "javascript", "runtime"]
excerpt: "Node.js is a runtime, not a framework or a language — the V8 engine pulled out of the browser and given the file system, the network, and the OS. Same language, different world."
date: 2026-08-13T03:27:58+0000
source: https://www.aveshina.my.id/en/blog/introduction-nodejs
---

"JavaScript on a server" was my Node.js summary, and it skipped the design choice that gives it its personality. The model that finally stuck: **Node.js is a runtime, not a framework and not a language.** It is the V8 JavaScript engine — the same one Chrome uses — pulled out of the browser and given a different set of capabilities: the file system, the network, the operating system. Same language, different world [1][2].

The design choice that defines everything else came in 2009, and it is the part I had to take seriously before the rest made sense.

## The one decision: non-blocking I/O

Ryan Dahl built Node.js to solve one specific problem — servers that waste time waiting. Reading a file, querying a database, calling another API over the network: all of these are I/O, and on a traditional server each one blocks a thread while it waits. A hundred users hitting the database means a hundred threads sitting idle, holding memory, doing nothing [3].

Node.js's answer was to make I/O **non-blocking by default**. Instead of waiting, the runtime kicks off the operation and immediately moves on to the next request. When the operation finishes, a callback fires. One thread, one process, many concurrent in-flight operations — because almost all of them were just waiting anyway. The single thread is only busy during the tiny slices where JavaScript actually runs.

```figure
<svg viewBox="0 0 740 280" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Thread-per-request blocking model versus single-thread non-blocking model. Top: three threads each waiting on I/O, holding memory while idle. Bottom: one thread rapidly cycling through three requests, each kicking off I/O and moving on; callbacks fire when I/O completes.">
  <defs>
    <marker id="narrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <text x="370" y="24" font-size="12" font-weight="700" fill="#7f1d1d" text-anchor="middle">Thread-per-request — threads block while waiting</text>
    <rect x="40" y="40" width="200" height="36" rx="6" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="140" y="62" font-size="10.5" font-family="ui-monospace, monospace" fill="#7f1d1d" text-anchor="middle">thread 1 ▸ waiting on file…</text>
    <rect x="270" y="40" width="200" height="36" rx="6" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="370" y="62" font-size="10.5" font-family="ui-monospace, monospace" fill="#7f1d1d" text-anchor="middle">thread 2 ▸ waiting on DB…</text>
    <rect x="500" y="40" width="200" height="36" rx="6" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="600" y="62" font-size="10.5" font-family="ui-monospace, monospace" fill="#7f1d1d" text-anchor="middle">thread 3 ▸ waiting on API…</text>
    <text x="370" y="100" font-size="10" font-style="italic" fill="#7f1d1d" text-anchor="middle">each idle thread still holds memory</text>

    <text x="370" y="140" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">Node.js — one thread, never blocks</text>
    <rect x="40" y="156" width="660" height="40" rx="6" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="370" y="180" font-size="10.5" font-family="ui-monospace, monospace" fill="#1e1b4b" text-anchor="middle">main thread ▸ req1 kickoff ▸ req2 kickoff ▸ req3 kickoff ▸ (idle, ready)</text>
    <rect x="40" y="208" width="200" height="36" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="140" y="230" font-size="10.5" font-family="ui-monospace, monospace" fill="#052e16" text-anchor="middle">callback ▸ file done</text>
    <rect x="270" y="208" width="200" height="36" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="370" y="230" font-size="10.5" font-family="ui-monospace, monospace" fill="#052e16" text-anchor="middle">callback ▸ DB done</text>
    <rect x="500" y="208" width="200" height="36" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="600" y="230" font-size="10.5" font-family="ui-monospace, monospace" fill="#052e16" text-anchor="middle">callback ▸ API done</text>
    <path d="M140,196 L140,206" stroke="#64748b" stroke-width="1.5" marker-end="url(#narrow)"/>
    <path d="M370,196 L370,206" stroke="#64748b" stroke-width="1.5" marker-end="url(#narrow)"/>
    <path d="M600,196 L600,206" stroke="#64748b" stroke-width="1.5" marker-end="url(#narrow)"/>
    <text x="370" y="266" font-size="10" font-style="italic" fill="#1e1b4b" text-anchor="middle">one thread cycles fast; I/O happens off-thread</text>
  </g>
</svg>
```

That contrast is the whole personality of Node.js. It is the reason Node.js shines for I/O-heavy workloads — chat servers, APIs, streaming, real-time — and the reason it struggles for CPU-heavy work like image processing or heavy computation, where the single thread actually has to do the math and can't hand it off [3].

## Same language, different environment

The second thing I had to nail down is that **Node.js and the browser run the same JavaScript, but expose different APIs.** The language — variables, functions, closures, prototypes — is identical because both use V8 [4]. What changes is the global object and the surrounding world:

- **Browser** gives me window, document (the DOM), localStorage, fetch pointed at web endpoints, the rendering pipeline. Its job is talking to a human through a page.
- **Node.js** gives me fs (the file system), http (raw network), process (the running OS process), Buffer (raw bytes), path. Its job is talking to a machine.

Code written for one does not run unchanged in the other. A script that reads document.querySelector throws in Node.js because there is no document. A script that calls fs.readFileSync throws in the browser because browsers sandbox files away from pages. The mental shift is simply: **same language, different toolbox.**

## Running Node.js code

Mechanically, Node.js is a binary I invoke from a terminal. The two entry points I use constantly:

- **node script.js** — run a file. The file's top-level code executes, and if nothing is keeping the event loop — the single queue the runtime keeps pulling tasks from — busy (an open server, a pending timer), the process exits when it finishes.
- **node with no argument** — drops into the **REPL**, a Read-Eval-Print Loop where I can type JavaScript line by line and see the result immediately. It is the fastest way to try an expression or inspect a value [5].

```
node -e "console.log(process.version)"
```

```
v20.11.0
```

That -e flag runs a one-liner. Between the file, the REPL, and the one-liner, there is almost always a fast way to get code running.

## A short history, because it explains the ecosystem

Node.js was released in 2009 by Ryan Dahl, who has since been candid about early design mistakes (including some he later corrected in Deno) [6]. In 2014 the project forked into **io.js** over governance concerns, and in 2015 the fork merged back under the Node.js Foundation — which is why the ecosystem today is unified rather than split. The npm registry, which launched alongside, grew into the largest package registry in any language, and that ecosystem size is a genuine reason Node.js gets picked for new projects today [2][3].

The point of knowing the history is practical: it explains why there are two module systems (CommonJS and ESM), why some packages still target old Node versions, and why the community occasionally debates foundational choices. The runtime I use today is the product of those forks and merges.

## How I use this

The takeaway I keep is a routing check, not a recommendation. When I reach for Node.js, I ask whether the workload is **I/O-bound** (waiting on files, databases, network, other services) or **CPU-bound** (number crunching, encoding, heavy parsing). I/O-bound → Node.js's single-threaded non-blocking model is a great fit, and I lean into async patterns. CPU-bound → the single thread becomes a bottleneck, and I either reach for Worker Threads or pick a different runtime. Most web servers, APIs, and CLIs are overwhelmingly I/O-bound, which is exactly why Node.js keeps showing up there.

## References

[1] OpenJS Foundation, "About Node.js," nodejs.org, 2024. [Online]. Available: [https://nodejs.org/en/about/](https://nodejs.org/en/about/)

[2] OpenJS Foundation, "Introduction to Node.js," nodejs.org, 2024. [Online]. Available: [https://nodejs.org/en/learn/getting-started/introduction-to-nodejs](https://nodejs.org/en/learn/getting-started/introduction-to-nodejs)

[3] M. Klein, "5 Reasons to Choose Node.js," Bitovi Blog, 2023. [Online]. Available: [https://www.bitovi.com/blog/5-reasons-to-choose-nodejs](https://www.bitovi.com/blog/5-reasons-to-choose-nodejs)

[4] OpenJS Foundation, "Differences between Node.js and the Browser," nodejs.org, 2024. [Online]. Available: [https://nodejs.org/en/learn/getting-started/differences-between-nodejs-and-the-browser/](https://nodejs.org/en/learn/getting-started/differences-between-nodejs-and-the-browser/)

[5] OpenJS Foundation, "Run Node.js scripts from the command line," nodejs.org, 2024. [Online]. Available: [https://nodejs.org/en/learn/command-line/run-nodejs-scripts-from-the-command-line/](https://nodejs.org/en/learn/command-line/run-nodejs-scripts-from-the-command-line/)

[6] "Node.js: The Documentary | An Origin Story," YouTube, 2023. [Online]. Available: [https://youtu.be/LB8KwiiUGy0](https://youtu.be/LB8KwiiUGy0)

```quiz
Q: Node.js is best described as…
- a JavaScript framework for building servers
- a JavaScript runtime built on the V8 engine, with non-blocking I/O
- a separate language that looks like JavaScript
correct: 1
explain: Node.js is a runtime — V8 plus a set of host APIs (fs, http, net). It is not a framework and not a new language.

Q: Why is Node.js well-suited to I/O-heavy workloads?
- It spawns a new thread per request so I/O runs in parallel
- It uses non-blocking I/O on a single thread, so waiting happens off-thread and the thread stays free
correct: 1
explain: Non-blocking I/O means the single main thread kicks off operations and moves on. The actual waiting happens in libuv's thread pool or the OS, and callbacks fire on the main thread when work completes.

Q: What is the key difference between running JavaScript in a browser versus in Node.js?
- The language is different
- The language is the same, but the host APIs differ — browser exposes DOM/window, Node exposes fs/http/process
correct: 1
explain: Both run JavaScript via V8. The difference is the surrounding world: browsers expose the DOM, Node exposes the file system, network, and OS process.

Q: Which workload is Node.js LEAST suited for out of the box?
- An HTTP API that fans out to several microservices
- A real-time chat server with many idle connections
- CPU-heavy image processing on every request
correct: 2
explain: CPU-bound work monopolizes the single main thread and blocks the event loop. Node.js shines for I/O-bound work; heavy computation needs Worker Threads or a different runtime.
```
