FullStack Course LogoFullStack Course
Module: Nodejs
Nodejs·116·9 MIN READ

116: Node.js Runtime Fundamentals, Versions, REPL, and Node vs Browser

TOPICS COVERED: Node.js Runtime Fundamentals, Versions, REPL, and Node vs Browser

Learning objectives

By the end of this lesson, you should be able to:

  • explain what Node.js is and what it is not;
  • describe the relationship between Node.js, V8, libuv, and operating-system APIs;
  • distinguish the Node runtime from the browser runtime;
  • choose an appropriate Node release line for production;
  • run scripts, evaluate snippets, and use the REPL;
  • understand the global environment, globalThis, and Node-specific globals;
  • explain synchronous versus asynchronous I/O at a high level;
  • identify workloads that are a good or poor fit for a single Node process;
  • inspect runtime, platform, architecture, and version information;
  • build a small command-line program without a framework.

Baseline for this course

Runtime versions change, so establish the baseline before relying on an example. As of August 2026, Node.js 24 is an LTS release line and Node.js 26 is the Current release line. For production applications, the normal choice is a supported LTS line unless the project has a deliberate reason to track Current.

This course uses modern Node APIs and ES modules. The examples assume a supported modern Node release, not patterns from the Node 12/14 era.

Check the versions installed on your machine:

bash
node --version
npm --version

You can inspect more detailed release metadata as well:

bash
node -p "process.version"
node -p "process.versions"
node -p "process.release"

Do not copy a runtime version from a tutorial without checking whether that version is still supported. A command that worked in an old tutorial may still run while carrying assumptions that are no longer appropriate for a supported project.

What Node.js is

The immediate problem Node solves is simple: JavaScript code sometimes needs to run without a browser. Node.js is a JavaScript runtime designed for that purpose.

A useful mental model is:

text
your JavaScript
      ↓
V8 JavaScript engine
      ↓
Node.js APIs + C/C++ bindings
      ↓
libuv / operating-system facilities
      ↓
files, sockets, timers, processes, threads

V8 parses, compiles, and executes JavaScript. Node supplies the host APIs and the native bindings that let that JavaScript interact with the operating system. libuv provides important cross-platform asynchronous I/O and event-loop infrastructure.

Node adds APIs such as:

text
node:fs
node:http
node:path
node:process
node:stream
node:worker_threads

That layered model is more accurate than thinking of Node as merely "Chrome JavaScript without a browser." V8 is the JavaScript engine; Node is the runtime around it, with its own host APIs and execution behavior.

Node.js versus a browser

The JavaScript language fundamentals overlap, but the host environment is different. Code can use the same language syntax and still depend on APIs that exist only in one runtime.

Browser APIs include:

text
document
window
localStorage
navigator
DOM events

Node APIs include:

text
process
Buffer
filesystem access
TCP/HTTP servers
child processes
worker threads

For example, this works in a browser because the browser supplies a DOM:

js
document.querySelector('#save');

document does not exist in ordinary Node execution.

Conversely, Node can read a file directly through its filesystem APIs:

js
import { readFile } from 'node:fs/promises';

const text = await readFile('./notes.txt', 'utf8');

That is not a browser capability. A browser may fetch data or ask the user to select a file, but it does not get unrestricted access to the server's filesystem.

Modern Node also implements many web-compatible APIs, including:

text
fetch
URL
AbortController
EventTarget
Web Streams
crypto web APIs

Shared names make code easier to move between environments, but they do not make the environments interchangeable. Identical APIs can still have different support levels, permissions, and deployment constraints. Check Node documentation and browser support separately.

globalThis

When code genuinely needs to refer to the global object, portable JavaScript can use:

js
globalThis

Node historically exposes:

js
global

New general-purpose code should prefer globalThis for a global reference. In most application code, however, the better choice is not to use a global at all.

Avoid storing application state on the global object:

js
globalThis.currentUser = ...

This creates hidden coupling. It also makes tests and concurrent work harder to reason about because unrelated code can read or replace that state without an explicit dependency.

Running Node code

Script file

Put JavaScript in a file when you want a repeatable program:

js
// hello.js
console.log('Hello from Node');

Run it with the Node executable:

bash
node hello.js

Evaluate an expression

For a one-off action, -e evaluates the supplied string as JavaScript:

bash
node -e "console.log(process.platform)"

The -p option evaluates an expression and prints its result:

bash
node -p "1 + 2"

Read from standard input

Node can also sit in a shell pipeline. This example reads standard input, converts each incoming chunk to uppercase, and writes it back to standard output:

bash
echo "hello" | node -e "
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => process.stdout.write(chunk.toUpperCase()));
"

Later lessons cover streams and CLI design in much more depth. For now, the useful distinction is between a persistent script file, a one-off expression, and a process consuming standard input.

The Node REPL

Run the executable without a script or expression:

bash
node

Then experiment with small expressions:

js
1 + 2
process.version
await Promise.resolve('ready')

The REPL is useful when you need a quick answer rather than a permanent program. Typical uses include:

  • checking a language expression;
  • inspecting built-in APIs;
  • trying a regular expression;
  • testing a path operation;
  • exploring an object.

It is not a replacement for repeatable tests. A successful experiment in the REPL is useful evidence, but it does not record setup, assertions, or the exact sequence someone else needs to reproduce the result.

Useful REPL commands include:

text
.history
.editor
.break
.clear
.exit

Exact commands can evolve between Node versions, so use .help in the version installed on your machine when you need the authoritative list.

Node's execution process

A Node program runs inside an operating-system process. That process is the unit the operating system starts, monitors, schedules, and eventually terminates.

Inspect some of its identifying information:

js
console.log({
  pid: process.pid,
  ppid: process.ppid,
  platform: process.platform,
  arch: process.arch,
  cwd: process.cwd(),
});

Example output might look like this:

js
{
  pid: 43120,
  ppid: 1220,
  platform: 'linux',
  arch: 'x64',
  cwd: '/srv/app'
}

The exact values depend on the machine and how the process was launched. The process has:

  • memory;
  • file descriptors;
  • environment variables;
  • signal handling;
  • current working directory;
  • exit status.

These are not abstract details once an application is deployed. They affect configuration, logging, file access, shutdown behavior, and how a supervisor decides whether a service succeeded or failed.

The main JavaScript thread

In ordinary Node execution, JavaScript runs on one main JavaScript thread. That does not mean Node can only do one thing at a time.

For many I/O operations, the sequence is roughly:

text
JavaScript starts operation
→ Node/libuv/OS waits for I/O
→ JavaScript can process other callbacks
→ completion is queued
→ callback/promise continuation runs later

The JavaScript thread is not occupied doing nothing while a socket or file operation waits. That is why this model works well for high-concurrency, I/O-heavy workloads. It also explains the boundary: once JavaScript itself begins a long synchronous computation, the thread is occupied and cannot move on to ordinary callbacks.

I/O-bound versus CPU-bound work

I/O-bound

I/O-bound work spends much of its time waiting for another system to respond. Examples include:

text
database calls
HTTP requests
file reads
socket waiting

Node can handle many concurrent operations in this category because waiting does not require the JavaScript thread to spin. The process can start one operation, continue with other work, and return to each operation when completion is available.

CPU-bound

CPU-bound work spends its time actively computing. Examples include:

text
large image transformations
video encoding
huge cryptographic loops
compression at scale
machine-learning computation
large synchronous parsing

A long synchronous computation blocks the JavaScript thread:

js
const start = Date.now();

while (Date.now() - start < 5000) {
  // blocks the process for ~5 seconds
}

console.log('finally');

For those five seconds, that same JavaScript thread cannot process ordinary request callbacks. Under load, one such operation can therefore delay unrelated requests, timers, and other application work.

Later you will use worker threads and processes for CPU-bound work. The point here is not that Node cannot perform computation; it is that long computation must not accidentally monopolize the thread responsible for handling other activity.

Synchronous APIs

Node exposes synchronous APIs as well as asynchronous ones:

js
import { readFileSync } from 'node:fs';

const data = readFileSync('./config.json', 'utf8');

Synchronous filesystem work can be reasonable when it happens during:

  • one-time CLI startup;
  • build scripts;
  • short administrative scripts.

The same choice is dangerous on a hot request path:

js
app.get('/report', (req, res) => {
  const template = readFileSync('./large-template.html');
  ...
});

Here, every request blocks the main thread while the read completes. Even if the file is usually fast to read, the blocking behavior becomes a scalability and latency risk when requests overlap or the underlying storage is slow.

Do not reduce the rule to "sync APIs are always bad." Ask where the blocking occurs and how often that code runs. A synchronous read during a one-time startup path has a different operational effect from the same read inside a handler serving many users.

Node's built-in module namespace

Use explicit built-in module specifiers:

js
import fs from 'node:fs';
import path from 'node:path';

The node: prefix makes it immediately clear that the import refers to a Node built-in module, not an npm package with a similar name. That clarity helps when reading code and reduces ambiguity about where the dependency comes from.

Later modules use this style consistently.

A first CLI

Create a file named task-summary.js:

js
// task-summary.js
const tasks = [
  { title: 'Review Node', completed: true },
  { title: 'Learn modules', completed: false },
  { title: 'Build API', completed: false },
];

const completed = tasks.filter((task) => task.completed).length;
const open = tasks.length - completed;

console.log(`Total: ${tasks.length}`);
console.log(`Completed: ${completed}`);
console.log(`Open: ${open}`);

Run it with:

bash
node task-summary.js

This is ordinary JavaScript plus Node's process and runtime environment. No framework is required to make a useful command-line program.

Inspect runtime memory

Node exposes a snapshot of memory counters through the process object:

js
console.log(process.memoryUsage());

Typical properties include:

text
rss
heapTotal
heapUsed
external
arrayBuffers

These values describe a point in time. Do not treat one memory snapshot as a leak diagnosis. To investigate a suspected leak, compare trends under a repeatable workload and, when appropriate, use heap snapshots. Later lessons cover that process in more detail.

Exit status

A process communicates a result to its parent through an exit status. For example:

bash
node -e "process.exitCode = 7"
echo $?

On Windows shells, the syntax for inspecting the last exit code differs.

The usual convention is:

text
0   success
non-zero failure / special result

Libraries and CLIs should document meaningful non-zero codes when callers need to distinguish failure modes.

Prefer setting an exit code when the program can finish normal cleanup:

js
process.exitCode = 1;

rather than immediately terminating with:

js
process.exit(1);

An immediate process.exit(1) can terminate before pending output flushes and before other cleanup runs. Signal and shutdown design is covered later.

Common misconceptions

“Node is a framework”

No. Express, Fastify, NestJS, and Hono are frameworks or libraries built on top of Node.

“Node is single-threaded”

JavaScript normally runs on a main thread, but Node also uses operating-system asynchronous operations, a libuv thread pool for selected work, and can create worker threads and child processes.

“Async means parallel CPU execution”

No. A Promise changes how completion is represented and scheduled; it does not move CPU-heavy JavaScript to another thread.

“Node and browser JavaScript are the same platform”

The language overlaps, but the host APIs differ.

“The latest Current release is always best for production”

Production often prioritizes the stability and support policy of an LTS release line.

Debug lab

Create this program:

js
console.log('A');

setTimeout(() => {
  console.log('B');
}, 0);

console.log('C');

Predict the output before you run it. Making the prediction first gives you something concrete to compare with the runtime behavior.

Then add:

js
Promise.resolve().then(() => {
  console.log('promise');
});

Do not fully explain the ordering yet. Lesson 119 develops the event loop and microtask model that accounts for it.

Exercises

Foundation

  1. Install a supported Node version with a version manager.
  2. Print process.version, process.platform, and process.arch.
  3. Run a script using node.
  4. Evaluate an expression with node -p.
  5. Use the REPL to inspect process.versions.

Intermediate

  1. Build a task-summary CLI that accepts an in-memory array.
  2. Add an invalid state and set a non-zero process.exitCode.
  3. Compare synchronous and asynchronous file reads for a 50 MB file.

Architecture

For each workload, decide whether a single Node process is a natural fit:

  • REST API waiting on PostgreSQL;
  • WebSocket chat;
  • image transcoding;
  • static file hashing CLI;
  • CPU-heavy PDF generation;
  • proxy service calling upstream APIs.

Explain the reasoning. In particular, distinguish time spent waiting on I/O from time spent consuming CPU, and consider whether work should be moved to worker threads, child processes, or a separate service.

Mastery checklist

You should be able to explain:

  • runtime versus language;
  • V8 versus Node versus libuv;
  • browser versus Node host APIs;
  • process versus JavaScript thread;
  • I/O-bound versus CPU-bound;
  • why synchronous work can block a server;
  • LTS versus Current;
  • what globalThis, process, and node: imports represent.

Official references

Reader page: /nodejs/lesson/116/node-js-runtime-fundamentals-versions-repl-and-node-vs-browser