116: 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
As of August 2026, Node.js 24 is an LTS release line while Node.js 26 is the Current release line. Production applications should normally choose a supported LTS line unless a project has a deliberate reason to track Current.
This course uses modern Node APIs and ES modules. Examples assume a supported modern Node release rather than legacy Node 12/14-era patterns.
Check:
node --version npm --version
Inspect release metadata:
node -p "process.version"
node -p "process.versions"
node -p "process.release"
Do not copy a runtime version from a tutorial without checking whether it is still supported.
What Node.js is
Node.js is a JavaScript runtime designed to execute JavaScript outside the browser.
A useful mental model is:
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 adds APIs such as:
node:fs node:http node:path node:process node:stream node:worker_threads
libuv provides important cross-platform asynchronous I/O and event-loop infrastructure.
Node is therefore more than “Chrome JavaScript without a browser.”
Node.js versus a browser
JavaScript language fundamentals are shared, but the host environment is different.
Browser APIs include:
document window localStorage navigator DOM events
Node APIs include:
process Buffer filesystem access TCP/HTTP servers child processes worker threads
This works in a browser:
document.querySelector('#save');
It does not exist in ordinary Node execution.
This works in Node:
import { readFile } from 'node:fs/promises';
const text = await readFile('./notes.txt', 'utf8');
It does not represent a browser capability.
Modern Node also implements many web-compatible APIs such as:
fetch URL AbortController EventTarget Web Streams crypto web APIs
Do not assume that identical names always mean identical deployment constraints. Check Node documentation and browser support separately.
globalThis
Portable JavaScript can reference the global object through:
globalThis
Node historically exposes:
global
but new general-purpose code should prefer globalThis when a global reference is truly required.
Avoid putting application state on the global object:
globalThis.currentUser = ...
That creates hidden coupling and makes testing/concurrency harder.
Running Node code
Script file
// hello.js
console.log('Hello from Node');
Run:
node hello.js
Evaluate an expression
node -e "console.log(process.platform)"
Print an expression
node -p "1 + 2"
Read from standard input
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 deeply.
The Node REPL
Run:
node
Then experiment:
1 + 2
process.version
await Promise.resolve('ready')
The REPL is useful for:
- 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.
Useful REPL concepts include:
.history .editor .break .clear .exit
Exact commands can evolve; use .help in your installed version.
Node's execution process
A Node program runs inside an operating-system process.
Inspect:
console.log({
pid: process.pid,
ppid: process.ppid,
platform: process.platform,
arch: process.arch,
cwd: process.cwd(),
});
Example:
{
pid: 43120,
ppid: 1220,
platform: 'linux',
arch: 'x64',
cwd: '/srv/app'
}
The process has:
- memory;
- file descriptors;
- environment variables;
- signal handling;
- current working directory;
- exit status.
These become important for production services.
The main JavaScript thread
Ordinary Node JavaScript executes on one main JavaScript thread.
That does not mean Node can only do one thing at a time.
For many I/O operations:
JavaScript starts operation → Node/libuv/OS waits for I/O → JavaScript can process other callbacks → completion is queued → callback/promise continuation runs later
This model is excellent for high-concurrency I/O-heavy workloads.
I/O-bound versus CPU-bound work
I/O-bound
Examples:
database calls HTTP requests file reads socket waiting
Node can handle many concurrent operations because waiting does not require the JavaScript thread to spin.
CPU-bound
Examples:
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:
const start = Date.now();
while (Date.now() - start < 5000) {
// blocks the process for ~5 seconds
}
console.log('finally');
During those five seconds, the same JavaScript thread cannot process ordinary request callbacks.
Later you will use worker threads/processes for CPU-bound work.
Synchronous APIs
Node exposes synchronous APIs:
import { readFileSync } from 'node:fs';
const data = readFileSync('./config.json', 'utf8');
Synchronous filesystem work may be reasonable during:
- one-time CLI startup;
- build scripts;
- short administrative scripts.
It is dangerous on a hot request path:
app.get('/report', (req, res) => {
const template = readFileSync('./large-template.html');
...
});
because every request blocks the main thread during the read.
Do not memorize “sync APIs are always bad.” Understand where blocking occurs.
Node's built-in module namespace
Prefer explicit built-in specifiers:
import fs from 'node:fs';
import path from 'node:path';
The node: prefix makes it obvious that the import is a built-in module rather than an npm package.
Later modules use this style consistently.
A first CLI
Create:
// 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:
node task-summary.js
This is ordinary JavaScript plus Node's process/runtime.
Inspect runtime memory
console.log(process.memoryUsage());
Typical properties include:
rss heapTotal heapUsed external arrayBuffers
Do not treat a single memory snapshot as a leak diagnosis. Later you will compare trends and heap snapshots.
Exit status
Run:
node -e "process.exitCode = 7"
echo $?
On Windows shells the syntax for inspecting the last exit code differs.
Conventions:
0 success non-zero failure / special result
Libraries/CLIs should document meaningful codes.
Prefer:
process.exitCode = 1;
when code can finish normal cleanup, rather than calling:
process.exit(1);
which can terminate before pending output flushes.
Signal/shutdown design is covered later.
Common misconceptions
“Node is a framework”
No. Express, Fastify, NestJS, and Hono are frameworks/libraries on top of Node.
“Node is single-threaded”
JavaScript normally runs on a main thread, but Node uses OS asynchronous operations, a libuv thread pool for selected work, and can create worker threads/child processes.
“Async means parallel CPU execution”
No. A Promise does not move CPU-heavy JavaScript to another thread.
“Node and browser JavaScript are the same platform”
The language overlaps; host APIs differ.
“The latest Current release is always best for production”
Production often prioritizes supported LTS stability.
Debug lab
Create:
console.log('A');
setTimeout(() => {
console.log('B');
}, 0);
console.log('C');
Predict output before running.
Then add:
Promise.resolve().then(() => {
console.log('promise');
});
Do not fully explain ordering yet; lesson 119 develops the event loop and microtask model.
Exercises
Foundation
- Install a supported Node version with a version manager.
- Print
process.version,process.platform, andprocess.arch. - Run a script using
node. - Evaluate an expression with
node -p. - Use the REPL to inspect
process.versions.
Intermediate
- Build a task-summary CLI that accepts an in-memory array.
- Add an invalid state and set a non-zero
process.exitCode. - 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.
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, andnode:imports represent.
