Node.js — the runtime (practical tooling view)

Status: 🟩 COMPLETE (🟦 LIVING — Node ships a new major every year) Last updated: 2026-06-19 Plain-English tagline: The Node side of “Node is on my machine” — installation, version management (nvm/fnm/volta), the node/npm/npx CLIs, and the practical details of keeping Node working across projects.


In plain English

Node.js is the program that lets JavaScript run outside the browser. That entry covers WHAT Node IS — the event loop, the runtime model, the language ecosystem. This entry covers everything PRACTICAL about Node as a tool on your machine:

  • How to install it (and which version)
  • How to manage multiple versions (nvm, fnm, volta)
  • What node, npm, npx, corepack do
  • Where Node lives on disk
  • How the project’s required Node version is declared
  • How to upgrade safely

If you’re following along with a tutorial and it says “make sure you have Node 22+ installed,” THIS is the entry you want. The conceptual entry is for understanding the runtime model.

In 2026, the practical Node landscape is:

  • Node 22 LTS is the safe default — supported until 2027
  • Node 24 is the latest, becomes LTS in October 2026
  • Node 26 is the bleeding edge
  • Bun and Deno are alternative runtimes — useful niches, not yet Node replacements for most webapp work

For Bible Quest-style projects: install Node 22 LTS via nvm-windows (Windows) or fnm (Mac/Linux), use it daily, upgrade to the next LTS when it ships.


Why it matters

Three concrete reasons the tooling layer matters:

  1. Tutorials fail when Node versions mismatch. “Run npm create-next-app” assumes a recent Node. On an old Node, you get cryptic errors. Knowing your version + how to switch saves debugging time.

  2. Multiple projects need different versions. A project from 2022 may need Node 18; a brand-new project may need Node 22+. Version managers let you switch without uninstalling.

  3. Vercel and CI use specific Node versions. Local dev on Node 24 + Vercel deploying with Node 20 = subtle bugs. Lock the version per project; sync local + CI + production.

The trade-off: version managers add a layer of indirection. Most newcomers don’t need one for the first month — installing Node directly is fine. They become essential once you have 2+ projects with different version requirements.


Installing Node — the three main paths

Path 1: Direct installer (simplest, no version switching)

Download from nodejs.org and run the installer. Two variants:

  • LTS (Long-Term Support) — for production. Currently Node 22.
  • Current — latest features. Currently Node 24.

For most users: install the LTS.

Verify:

node --version          # v22.x.x
npm --version           # 10.x.x
npx --version           # 10.x.x

Drawback: only one Node version installed at a time. If a project needs Node 18 and another needs Node 22, you’d uninstall + reinstall constantly. Hence version managers.

Install ONE version manager. From then on, multiple Node versions coexist; you switch per project.

The options:

ToolOSNotes
nvm (Mac/Linux)macOS, LinuxOriginal; bash-based; slow to switch
nvm-windowsWindowsSeparate project from nvm; similar UI
fnm (Fast Node Manager)AllRust-based; much faster than nvm
voltaAllAuto-switches per project (reads package.json); pins tool versions
asdfMac/LinuxMulti-language manager (Node, Ruby, Python, etc.)
miseAllModern asdf successor; growing fast

For new setups in 2026:

  • Windows: nvm-windows (familiar) or fnm
  • macOS/Linux: fnm (speed) or volta (auto-switching) or mise (multi-language)

Installing fnm

# macOS via Homebrew
brew install fnm
 
# Windows via winget
winget install Schniz.fnm
 
# Linux / manual
curl -fsSL https://fnm.vercel.app/install | bash

Then add to your shell profile (~/.zshrc, ~/.bashrc, or PowerShell $PROFILE):

eval "$(fnm env --use-on-cd)"

The --use-on-cd flag auto-switches when you cd into a project with a .nvmrc file. Magic.

Install Node versions:

fnm install 22                 # latest 22.x
fnm install 22.11.0            # specific
fnm install --lts              # latest LTS
 
fnm use 22                     # switch to 22
fnm default 22                 # default for new shells
 
fnm list                       # show installed versions
fnm list-remote                # show available versions

Installing nvm-windows

winget install CoreyButler.NVMforWindows

Reboot or open a new PowerShell. Then:

nvm install 22.11.0
nvm use 22.11.0
nvm list

nvm-windows lacks fnm’s auto-switching but is reliable and well-known.

Path 3: System package manager

  • macOS: brew install node
  • Linux (Debian/Ubuntu): apt install nodejs npm
  • Linux (Fedora): dnf install nodejs npm
  • Windows: winget install OpenJS.NodeJS.LTS

Works for a single Node version. Switching versions is painful (uninstall + reinstall). Use for quickly bootstrapping; switch to a version manager once you do real work.


What’s included when you “install Node”

A Node install ships several CLIs:

CommandWhat it is
nodeThe JavaScript runtime itself. Runs JS files: node script.js. Also has a REPL: node with no args opens an interactive prompt.
npmThe default package manager. Installs dependencies, runs scripts. Always installed with Node.
npxRuns a package’s binary on-the-fly. npx create-next-app downloads + runs without globally installing.
corepackManages “alternative” package managers (pnpm, yarn). Built into Node 16.10+. Run corepack enable to make pnpm and yarn available.

For deeper coverage of npm specifically, see npm & package managers.


Locking the Node version per project

Three places to declare which Node version a project needs:

1. .nvmrc — for nvm / fnm / asdf

Plain text file at project root with just the version:

22.11.0

When you cd into the project (and have fnm set up with auto-switching), the right version activates automatically.

2. package.json’s engines.node field

{
  "engines": {
    "node": ">=22"
  }
}

Vercel reads this; npm reads this; CI tools read this. The semver range is “any Node 22+.”

Stricter:

"engines": {
  "node": "22.11.0"
}

A specific version. Tighter, but breaks more easily.

3. volta config in package.json

If using volta:

{
  "volta": {
    "node": "22.11.0",
    "npm": "10.9.0"
  }
}

Volta auto-switches AND pins the tool version exactly. The strictest reproducibility option.

For Bible Quest-style projects: set engines.node in package.json (so Vercel picks it up) AND commit a .nvmrc (so local dev tools auto-switch). Belt and suspenders.


A concrete example: setting up a new machine for Bible Quest

# 1. Install fnm
winget install Schniz.fnm
 
# 2. Reload PowerShell. Verify
fnm --version
 
# 3. Install Node 22 LTS
fnm install --lts
fnm default 22
 
# 4. Verify
node --version              # v22.x.x
npm --version               # 10.x.x
 
# 5. Clone the project
git clone https://github.com/geo-au/st-marks-bible-quest.git
cd st-marks-bible-quest
 
# 6. fnm picks up .nvmrc (if present); install deps
fnm use                     # reads .nvmrc and switches
npm install
 
# 7. Run dev
npm run dev

Done. Five-minute setup. Future projects with different Node requirements switch automatically.


Upgrading Node — the safe path

Node 22 LTS is supported until April 2027. Once you’re on it, you don’t NEED to upgrade for years. But you’ll want to occasionally:

  • Security patches (always apply within 22.x — same major version)
  • Move to a new LTS (24, 26 — every two years)
  • Try the latest before going LTS (Node 25, 27 — short-lived)

The safe pattern:

  1. Read the release notes for any breaking changes
  2. Update engines.node in package.json to the new version
  3. Update .nvmrc
  4. Run npm install (some packages may have peer deps tied to Node version)
  5. Run the full test suite + production build
  6. Push to a branch; check the Vercel preview
  7. Merge if green

For Bible Quest, this is a 30-minute job per upgrade. Don’t skip the test-and-build steps — Node minor versions can surface subtle bugs (deprecated APIs, slightly different fetch behavior).


Native modules and platform binaries

Some npm packages have native code that compiles per-platform:

  • sharp — image processing (uses libvips C library)
  • bcrypt — password hashing (with optional C++ binary)
  • canvas — image generation (uses cairo)
  • better-sqlite3 — SQLite database

Modern packages ship prebuilt binaries for major platforms. Install works without compilation. But:

  • A npm install on Mac might download Mac binaries; the same node_modules deployed to Linux fails
  • CI runs npm ci --omit=dev which re-installs, fetching the right binaries
  • npm rebuild <package> forces re-fetching binaries (useful if you copy node_modules between machines — but don’t do that)

For Vercel deployments: this is handled automatically. The build server is Linux, fetches Linux binaries, deploys those. You don’t think about it.


REPL — Node’s interactive prompt

Run node with no arguments:

$ node
Welcome to Node.js v22.11.0.
Type ".help" for more information.
> 1 + 1
2
> Math.random()
0.7234829...
> const x = { a: 1, b: 2 }
> x.a
1
> .exit

This is the REPL (Read-Eval-Print Loop). Great for:

  • Testing a snippet of JavaScript
  • Computing one-offs
  • Exploring API responses (await fetch(...))
  • Verifying a regex
  • Calculating something quickly

Type .help for special commands; .exit or Ctrl+C twice to leave.

Modern alternatives: node --watch script.js for auto-reload during development, or just use VS Code’s debugger.


Running a script

# Run a JS file
node script.js
 
# With auto-reload on changes (Node 22+)
node --watch script.js
 
# With TypeScript directly (Node 22.6+ with --experimental-strip-types, stable in Node 24)
node --experimental-strip-types script.ts
 
# Or use tsx (third-party, fully supported)
npx tsx script.ts
 
# Run with env vars
NODE_ENV=production node script.js                    # bash
$env:NODE_ENV="production"; node script.js            # PowerShell
 
# Or with the --env-file flag (Node 20.6+)
node --env-file=.env.local script.js
 
# Debug
node --inspect-brk script.js                          # opens DevTools-compatible debugger

For framework projects (Next.js, etc.), you don’t run node directly. The framework’s CLI (e.g., next dev) wraps it. But knowing the raw node invocations is useful for scripts, automation, and debugging.


TypeScript without a build step

Node 22.6+ has built-in TypeScript support (experimental). Node 24 makes it stable:

node --experimental-strip-types script.ts            # Node 22.6+
node script.ts                                        # Node 24+

This STRIPS type annotations and runs the resulting JavaScript. It does NOT type-check — you’d still need tsc --noEmit for that.

For development scripts, this means you can write TypeScript and run it directly. Previously you needed ts-node, tsx, or a build step.

For framework projects, the framework’s bundler handles TypeScript. This native support matters for utility scripts, CLI tools, and one-off automation.


Common gotchas

  • Node --version shows minor mismatch issues. Node 22.11.0 vs 22.0.0 are both “Node 22” — most code works in both. But occasionally a feature lands in a minor release that breaks if you’re on the wrong sub-version. When tutorials say “Node 22.5+”, that’s a real constraint.

  • node --version vs npm --version are different. Each has its own version. npm updates separately from Node. To upgrade npm: npm install -g npm@latest.

  • npx <pkg> runs the latest version by default. It’s “do whatever the latest version of this CLI says.” For reproducibility in scripts, pin: npx create-next-app@latest.

  • Global installs (npm install -g) are usually wrong. Use npx for one-off runs; use project devDependencies for repeated use. Global installs pollute the system and pin you to one version.

  • PATH issues are the #1 install problem. After installing Node, if node --version says “command not found,” the installer didn’t update PATH. Open a NEW terminal (PATH refreshes); or set it manually.

  • fnm doesn’t auto-activate without setup. Forgetting eval "$(fnm env --use-on-cd)" in your shell profile means fnm is installed but never picks up .nvmrc files. Add the line.

  • Multiple Node installs conflict. A Homebrew Node + a fnm Node + a system Node = three competing node binaries. Whichever is FIRST in PATH wins. Pick one path; uninstall the others.

  • node_modules is version-specific in some cases. Native packages compile against a specific Node ABI. Upgrading Node may require npm rebuild (or just deleting + reinstalling node_modules).

  • engines.node is a soft constraint by default. A Node 24-only project installs on Node 18 with just a warning. To make it strict: npm config set engine-strict true (per user) or use Volta (auto-enforces).

  • Vercel reads engines.node and picks the matching available version. If you say >=22, Vercel uses Node 22. If you say 24, Vercel uses Node 24 (assuming it’s released).

  • process.versions includes more than node. process.versions.v8, process.versions.openssl, etc. — sometimes useful for debugging.

  • Long Node REPL output truncates. console.log on a huge object cuts off. Use JSON.stringify(obj, null, 2) or util.inspect(obj, { depth: null }) for the full view.

  • node --watch watches the entry file, not all changes. It re-runs on saves to the file you launched. For broader watching, use a tool like nodemon or framework-built watch modes.

  • --env-file=.env doesn’t override existing env vars. A var already in process.env keeps its value; .env only fills gaps. For testing, this is usually what you want.

  • process.exit(1) flushes asynchronously. If you call process.exit() mid-async-write, output may be lost. Use process.exitCode = 1 instead and let the process exit naturally.

  • Node memory limit defaults to ~4GB. A very large build may hit it. Increase with NODE_OPTIONS="--max-old-space-size=8192".

  • __dirname and __filename don’t exist in ESM. Use import.meta.url + fileURLToPath (see Node.js concept entry).

  • fetch was experimental in Node 18, stable in Node 22. If you write modern code using fetch, you need Node 22+ (or a polyfill).

  • The node: prefix for built-in modules is the modern style. import fs from "node:fs" is preferred over import fs from "fs". Both work; the prefix is clearer.

  • corepack enable makes pnpm and yarn available. Without it, pnpm is “command not found” even though it ships with Node. Run once per machine.

  • Different Node versions ship different default OpenSSL. This rarely matters but can affect TLS-related behavior (a cert that works on Node 22 fails on Node 18 due to older OpenSSL).

  • Windows path length limits affect deeply-nested node_modules. Modern Node + Long Path support (Developer Mode in Windows 11) handles this; old Windows + old Node breaks.

  • npm cache lives in ~/.npm (Mac/Linux) or %APPDATA%\npm-cache (Windows). Sometimes corrupts. npm cache clean --force fixes weird install errors.

  • node --inspect opens a debug port (9229 by default). Chrome DevTools or VS Code can connect to it. Useful but the port should never be exposed publicly — it grants RCE.

  • CommonJS vs ESM friction. Some packages are CJS-only, some are ESM-only, most are dual. Errors like “ERR_REQUIRE_ESM” or “Cannot use import statement outside a module” trace back to this. See the Node.js concept entry for the mechanics.

  • type: "module" in package.json flips files to ESM mode. Without it, .js is CJS. With it, .js is ESM. Use .cjs for CJS within an ESM project or vice versa.

  • AI tools often install global CLIs unnecessarily. Claude or other AI suggesting npm install -g vercel works but pollutes globals. npx vercel is usually better.

  • node_modules size is normal. 500MB-2GB for a real project. Don’t try to optimize it; bundlers strip it down for deployment.

  • Don’t use Node for CPU-bound tasks. Single-threaded; one heavy computation blocks everything. Use worker_threads, child processes, or a different runtime.

  • Don’t run npm install as root/admin. Permission issues with node_modules ownership downstream. Always run as your user.

  • Pinning to a specific Node minor version (engines.node: "22.11.0") is fragile. When CI installs 22.11.2, it warns or errors. Use ranges (>=22 or ^22) unless you have a specific reason for an exact pin.

  • Don’t put Node project files inside Program Files on Windows. Permissions cause weird failures. Keep projects in C:\Users\<you>\projects\ or similar user-writable location.


When you might want Bun or Deno instead

For most webapp work in 2026: Node is the safest choice.

Consider Bun when:

  • You want fastest install speed (10× faster than npm)
  • You’re building a Hono / Elysia-style minimal API server
  • You want TypeScript natively without setup
  • You’re starting fresh and willing to handle some ecosystem gaps

Consider Deno when:

  • You want secure-by-default permissions
  • You’re building command-line tools
  • You want web-standards-first (no Node-specific APIs)

For Bible Quest’s Next.js + Supabase stack: stick with Node. Both Bun and Deno have rough edges with Next.js. The day they don’t, switch is easy.


See also


Sources