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), thenode/npm/npxCLIs, 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,corepackdo - 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:
-
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. -
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.
-
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.xDrawback: 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.
Path 2: Version manager (recommended for any serious work)
Install ONE version manager. From then on, multiple Node versions coexist; you switch per project.
The options:
| Tool | OS | Notes |
|---|---|---|
| nvm (Mac/Linux) | macOS, Linux | Original; bash-based; slow to switch |
| nvm-windows | Windows | Separate project from nvm; similar UI |
| fnm (Fast Node Manager) | All | Rust-based; much faster than nvm |
| volta | All | Auto-switches per project (reads package.json); pins tool versions |
| asdf | Mac/Linux | Multi-language manager (Node, Ruby, Python, etc.) |
| mise | All | Modern asdf successor; growing fast |
For new setups in 2026:
- Windows:
nvm-windows(familiar) orfnm - macOS/Linux:
fnm(speed) orvolta(auto-switching) ormise(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 | bashThen 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 versionsInstalling nvm-windows
winget install CoreyButler.NVMforWindowsReboot or open a new PowerShell. Then:
nvm install 22.11.0
nvm use 22.11.0
nvm listnvm-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:
| Command | What it is |
|---|---|
node | The JavaScript runtime itself. Runs JS files: node script.js. Also has a REPL: node with no args opens an interactive prompt. |
npm | The default package manager. Installs dependencies, runs scripts. Always installed with Node. |
npx | Runs a package’s binary on-the-fly. npx create-next-app downloads + runs without globally installing. |
corepack | Manages “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 devDone. 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:
- Read the release notes for any breaking changes
- Update
engines.nodeinpackage.jsonto the new version - Update
.nvmrc - Run
npm install(some packages may have peer deps tied to Node version) - Run the full test suite + production build
- Push to a branch; check the Vercel preview
- 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 installon Mac might download Mac binaries; the samenode_modulesdeployed to Linux fails - CI runs
npm ci --omit=devwhich re-installs, fetching the right binaries npm rebuild <package>forces re-fetching binaries (useful if you copynode_modulesbetween 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
> .exitThis 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 debuggerFor 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
--versionshows 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 --versionvsnpm --versionare different. Each has its own version.npmupdates 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. Usenpxfor 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 --versionsays “command not found,” the installer didn’t update PATH. Open a NEW terminal (PATH refreshes); or set it manually. -
fnmdoesn’t auto-activate without setup. Forgettingeval "$(fnm env --use-on-cd)"in your shell profile meansfnmis installed but never picks up.nvmrcfiles. Add the line. -
Multiple Node installs conflict. A Homebrew Node + a fnm Node + a system Node = three competing
nodebinaries. Whichever is FIRST in PATH wins. Pick one path; uninstall the others. -
node_modulesis version-specific in some cases. Native packages compile against a specific Node ABI. Upgrading Node may requirenpm rebuild(or just deleting + reinstallingnode_modules). -
engines.nodeis 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.nodeand picks the matching available version. If you say>=22, Vercel uses Node 22. If you say24, Vercel uses Node 24 (assuming it’s released). -
process.versionsincludes more thannode.process.versions.v8,process.versions.openssl, etc. — sometimes useful for debugging. -
Long Node REPL output truncates.
console.logon a huge object cuts off. UseJSON.stringify(obj, null, 2)orutil.inspect(obj, { depth: null })for the full view. -
node --watchwatches the entry file, not all changes. It re-runs on saves to the file you launched. For broader watching, use a tool likenodemonor framework-built watch modes. -
--env-file=.envdoesn’t override existing env vars. A var already inprocess.envkeeps its value;.envonly fills gaps. For testing, this is usually what you want. -
process.exit(1)flushes asynchronously. If you callprocess.exit()mid-async-write, output may be lost. Useprocess.exitCode = 1instead 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". -
__dirnameand__filenamedon’t exist in ESM. Useimport.meta.url+fileURLToPath(see Node.js concept entry). -
fetchwas experimental in Node 18, stable in Node 22. If you write modern code usingfetch, 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 overimport fs from "fs". Both work; the prefix is clearer. -
corepack enablemakes pnpm and yarn available. Without it,pnpmis “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 cachelives in~/.npm(Mac/Linux) or%APPDATA%\npm-cache(Windows). Sometimes corrupts.npm cache clean --forcefixes weird install errors. -
node --inspectopens 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"inpackage.jsonflips files to ESM mode. Without it,.jsis CJS. With it,.jsis ESM. Use.cjsfor CJS within an ESM project or vice versa. -
AI tools often install global CLIs unnecessarily. Claude or other AI suggesting
npm install -g vercelworks but pollutes globals.npx vercelis usually better. -
node_modulessize 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 installas root/admin. Permission issues withnode_modulesownership 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 (>=22or^22) unless you have a specific reason for an exact pin. -
Don’t put Node project files inside
Program Fileson Windows. Permissions cause weird failures. Keep projects inC:\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
- Node.js (concept) 🟩 🟦 — what Node IS, event loop, async model
- npm & package managers 🟩 — the package manager that ships with Node
- VS Code 🟩 🟦 — uses Node’s TypeScript service
- Windows dev environment 🟩 — Windows-specific Node install quirks
- Terminals & emulators 🟩 🟦
- PowerShell vs Bash 🟩 — where you run
nodecommands - Dotfiles & config 🟩 —
.nvmrc, shell profile setup - Environment variables 🟩 — Node reads
process.env - Serverless functions 🟩 — Vercel functions are Node
- CD 🟩 — CI installs Node per project’s
engines - JavaScript 🟩
- TypeScript 🟩
- Glossary: Node.js, LTS, REPL, nvm
Sources
- Node.js docs
- Node.js release schedule — when each version reaches end-of-life
- fnm — the modern version manager
- nvm-windows
- nvm (Unix)
- Volta
- mise — modern multi-language version manager
- Node.js — Native TypeScript support
- Vercel — Node.js version