cheat sheet

Node.js Installation

Install Node.js on Windows, macOS, and Linux using winget, Homebrew, apt, nvm, Volta, or fnm. Covers version managers, LTS vs current, and verifying your install.

Node.js Installation

What it is

Node.js is a cross-platform JavaScript runtime built on Chrome's V8 engine. Installing it gives you the node binary for running scripts and the npm package manager. Most developers use a version manager (nvm, Volta, or fnm) rather than a system-level install so they can switch Node versions per project.

Install

Windows — winget

powershell
winget install --id OpenJS.NodeJS.LTS

Output:

text
Found Node.js (LTS) [OpenJS.NodeJS.LTS] Version 22.14.0
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it license, third-party packages.
Downloading https://nodejs.org/dist/v22.14.0/node-v22.14.0-x64.msi
Successfully installed

macOS — Homebrew

bash
brew install node

Output:

text
==> Downloading https://ghcr.io/v2/homebrew/core/node/manifests/22.14.0
==> Installing node
==> Summary
🍺  /usr/local/Cellar/node/22.14.0: 2,412 files, 71.5MB

Ubuntu / Debian — apt (via NodeSource)

bash
# Add the NodeSource v22 repository
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs

Output:

text
Selecting previously unselected package nodejs.
Setting up nodejs (22.14.0-1nodesource1) ...

nvm is the most widely used approach for managing multiple Node versions. It installs Node entirely in your home directory — no sudo required.

Install nvm

bash
# macOS / Linux
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

Restart your shell (or source ~/.bashrc / source ~/.zshrc), then verify:

bash
nvm --version

Output:

text
0.39.7

Install and use a Node version

bash
# Install the latest LTS release
nvm install --lts

Output:

code
Downloading and installing node v22.14.0...
Downloading https://nodejs.org/dist/v22.14.0/node-v22.14.0-linux-x64.tar.xz...
Now using node v22.14.0 (npm v10.9.2)
Creating default alias: default -> lts/* (-> v22.14.0)
bash
# Install a specific version
nvm install 20.18.0

# Switch to a version
nvm use 20.18.0

# Switch back to latest LTS
nvm use --lts

# Set the default version for new shells
nvm alias default 22

# List installed versions
nvm ls

Output (nvm ls):

text
->     v22.14.0
       v20.18.0
default -> 22 (-> v22.14.0)
lts/* -> lts/jod (-> v22.14.0)

.nvmrc — pin a version per project

bash
# Create a .nvmrc file in your project root
echo "22" > .nvmrc

# Any developer with nvm can then run:
nvm use
# nvm reads .nvmrc and switches automatically

Output:

text
Found '/home/user/myproject/.nvmrc' with version <22>
Now using node v22.14.0 (npm v10.9.2)

Volta — toolchain manager

Volta pins Node versions per project in package.json and automatically switches when you cd into a directory. It works on Windows without WSL.

bash
# Install Volta (macOS / Linux)
curl https://get.volta.sh | bash
powershell
# Install Volta (Windows — run in PowerShell as admin)
winget install Volta.Volta
bash
# Install Node LTS and make it the default
volta install node

# Install a specific version
volta install node@20

# Pin a version to the current project (writes to package.json)
volta pin node@22

Output (volta install node):

text
success: installed and set node@22.14.0 (with npm@10.9.2) as default

Output (volta pin node@22):

text
success: pinned node@22.14.0 (with npm@10.9.2) in package.json

The package.json entry Volta adds:

json
{
  "volta": {
    "node": "22.14.0",
    "npm": "10.9.2"
  }
}

fnm — Fast Node Manager

fnm is written in Rust and is significantly faster than nvm at shell startup time. It also reads .nvmrc and .node-version files.

bash
# macOS
brew install fnm

# Linux (curl installer)
curl -fsSL https://fnm.vercel.app/install | bash

# Windows (winget)
winget install Schniz.fnm

Add the shell hook (add to ~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish):

bash
# bash / zsh
eval "$(fnm env --use-on-cd)"

# fish
fnm env --use-on-cd | source
bash
# Install and use the latest LTS
fnm install --lts
fnm use lts-latest

# Install a specific version
fnm install 20
fnm use 20

# List installed versions
fnm list

Output (fnm install --lts):

text
Installing Node v22.14.0 (x64)

Output (fnm list):

text
* v22.14.0 default (lts-latest)
  v20.18.0

Verify the installation

bash
node --version

Output:

text
v22.14.0
bash
npm --version

Output:

text
10.9.2

LTS vs Current — when to use each

ChannelExampleStabilityWho should use it
LTS (Long-Term Support)v22.x (Active), v20.x (Maintenance)High — bug + security fixes for 3 yearsProduction apps, team projects, CI
Currentv23.xNew features land here first; breaks possibleTrying new Node APIs, library authors

Use LTS for anything that runs in production. LTS versions have even major version numbers (18, 20, 22). Odd numbers (19, 21, 23) are Current releases and have a shorter support window.

Version manager comparison

FeaturenvmVoltafnm
Shell startup speedSlow (bash function)Fast (binary shim)Fast (Rust binary)
Windows supportWSL onlyYes (native)Yes (native)
Per-project pin.nvmrcpackage.json volta field.nvmrc / .node-version
Reads .nvmrcYesYesYes
Also manages npm/yarn/pnpmNoYesNo
Install without rootYesYesYes
Ecosystem maturityVery matureMatureGrowing

Pick nvm if you're on macOS/Linux and want maximum compatibility. Pick Volta if you're on Windows or want package.json-level pinning shared with the whole team. Pick fnm if shell startup time matters (e.g., you open many terminals).

n — minimal version manager

n is the smallest of the Node version managers — a single bash script with no shell hooks. It installs Node directly into the system Node prefix (default /usr/local), which is the cleanest layout but requires sudo unless you re-point the prefix. Reach for n when you want zero magic and no shell-startup overhead, on a machine where you're the only Node user.

bash
# Install n itself (one-off bootstrap with curl)
curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o /usr/local/bin/n
chmod +x /usr/local/bin/n

# Or via npm if you already have Node
npm install -g n

Output: (none — exits 0 on success)

bash
# Install the latest LTS
n lts

# Install a specific version
n 22.14.0

# Install latest current release
n latest

# Switch interactively (arrow keys)
n

# Remove a version
n rm 20.18.0

Output (n lts):

text
  installing : node-v22.14.0
       mkdir : /usr/local/n/versions/node/22.14.0
       fetch : https://nodejs.org/dist/v22.14.0/node-v22.14.0-linux-x64.tar.xz
   installed : v22.14.0 (with npm 10.9.2)

To avoid sudo, re-point the prefix at a user-owned directory:

bash
# Add to ~/.bashrc or ~/.zshrc
export N_PREFIX="$HOME/.n"
export PATH="$N_PREFIX/bin:$PATH"
bash
echo $N_PREFIX

Output:

text
/home/alice/.n

asdf — multi-language version manager

asdf manages versions for many runtimes (Node, Python, Ruby, Go, Terraform…) through a plugin system. One tool, one config file (.tool-versions), one PATH manipulation. Reach for asdf when you switch between multiple languages and want one cohesive setup; the trade-off is slower shell startup than fnm/mise.

bash
# Install asdf via git
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0

# Add to ~/.bashrc / ~/.zshrc
. "$HOME/.asdf/asdf.sh"
. "$HOME/.asdf/completions/asdf.bash"

Restart your shell, then install the Node plugin:

bash
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git

# Install a Node version
asdf install nodejs latest
asdf install nodejs 22.14.0

# Set the global default
asdf global nodejs 22.14.0

# Set a project-local version (writes .tool-versions)
asdf local nodejs 20.18.0

# List installed
asdf list nodejs

Output (asdf list nodejs):

text
  20.18.0
 *22.14.0

The .tool-versions file is multi-runtime:

text
nodejs 22.14.0
python 3.12.7
ruby 3.3.5
terraform 1.9.5

mise — modern asdf-compatible manager

mise (formerly rtx) is a Rust-based, asdf-compatible runtime manager. It reads the same .tool-versions files but has a much faster startup, more features (env management, tasks), and a richer plugin ecosystem. Pick mise over asdf for new projects.

bash
# macOS — Homebrew
brew install mise

# Linux / WSL — curl installer
curl https://mise.run | sh

# Windows — winget
winget install jdx.mise

Activate in your shell (one-time):

bash
# bash / zsh — add to rc file
eval "$(mise activate bash)"        # or zsh

# fish
mise activate fish | source

Use it identically to asdf:

bash
mise use --global node@22
mise use node@20.18.0       # project-local (writes .mise.toml or .tool-versions)
mise install                # install everything in .mise.toml
mise list                   # show installed versions
mise current                # show what's active in the current dir

Output (mise current):

text
node@22.14.0
python@3.12.7

mise's project config:

toml
# .mise.toml
[tools]
node = "22"
python = "3.12"

[env]
NODE_ENV = "development"
DATABASE_URL = "postgres://localhost/dev"

[tasks.dev]
run = "npm run dev"
bash
mise run dev

Output: (none — exits 0 on success)

Manual install (no version manager)

Sometimes you want a single Node version, system-installed, with no manager overhead. The official downloads are the simplest path — pre-compiled binaries straight from nodejs.org.

bash
# Linux x64 — download + extract to /usr/local
VER=v22.14.0
curl -fsSL "https://nodejs.org/dist/$VER/node-$VER-linux-x64.tar.xz" \
  | sudo tar -xJ -C /usr/local --strip-components=1

Output: (none — exits 0 on success)

bash
which node
node --version

Output:

text
/usr/local/bin/node
v22.14.0
powershell
# Windows — MSI installer
winget install OpenJS.NodeJS.LTS

# Or download the .msi from https://nodejs.org and double-click

Output: (none — exits 0 on success)

bash
# macOS — pkg installer (preferred for "system" install)
curl -O "https://nodejs.org/dist/v22.14.0/node-v22.14.0.pkg"
sudo installer -pkg node-v22.14.0.pkg -target /

Output: (none — exits 0 on success)

Linux distribution packages

Distro packages (apt, dnf, pacman) are convenient but ship older Node versions than nodejs.org. Use them only when you must — for example on a server where you can't run network installers.

Debian / Ubuntu

bash
# Default repos (often old — e.g. Ubuntu 24.04 ships Node 18)
sudo apt-get update
sudo apt-get install -y nodejs npm

# NodeSource (current LTS)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs

Output:

text
Setting up nodejs (22.14.0-1nodesource1) ...

Fedora / RHEL / Rocky

bash
# Default dnf module (older Node)
sudo dnf module install nodejs:22/common

# NodeSource (current LTS)
curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash -
sudo dnf install -y nodejs

Output: (none — exits 0 on success)

Arch / Manjaro

bash
sudo pacman -S nodejs npm

Output: (none — exits 0 on success)

Alpine (for Docker base images)

bash
apk add --no-cache nodejs npm

Output: (none — exits 0 on success)

Path layout reference

Where Node lives after each install method — useful when diagnosing PATH conflicts or "wrong version" issues.

Install methodNode binarynpm prefixGlobal modules
Official .pkg (macOS)/usr/local/bin/node/usr/local/usr/local/lib/node_modules/
Homebrew (macOS Apple Silicon)/opt/homebrew/bin/node/opt/homebrew/opt/homebrew/lib/node_modules/
Homebrew (macOS Intel)/usr/local/bin/node/usr/local/usr/local/lib/node_modules/
winget (Windows)C:\Program Files\nodejs\node.exeC:\Program Files\nodejs%APPDATA%\npm\node_modules\
apt / dnf (Linux)/usr/bin/node/usr/usr/lib/node_modules/
nvm~/.nvm/versions/node/v<X>/bin/node~/.nvm/versions/node/v<X>~/.nvm/versions/node/v<X>/lib/node_modules/
fnm~/.local/share/fnm/node-versions/v<X>/installation/bin/node(same prefix)(same prefix)/lib/node_modules/
Volta~/.volta/tools/image/node/<X>/bin/node(managed by Volta)(managed by Volta)
asdf / mise~/.asdf/installs/nodejs/<X>/bin/node(same prefix)(same prefix)/lib/node_modules/

Inspect what's currently on PATH:

bash
which -a node

Output:

text
/home/alice/.nvm/versions/node/v22.14.0/bin/node
/usr/bin/node

The first entry wins. If the wrong version runs, it's a PATH ordering bug.

Switching package manager via Corepack

Corepack ships with Node 16.9+. It activates the package manager declared in your package.json packageManager field, so every contributor uses the exact same version.

bash
# One-time enable (idempotent)
corepack enable

# Prepare a specific version
corepack prepare pnpm@9.12.1 --activate
corepack prepare yarn@4.5.1 --activate
corepack prepare npm@10.9.2 --activate

Output: (none — exits 0 on success)

json
{
  "packageManager": "pnpm@9.12.1"
}

Now pnpm install in this repo always uses pnpm 9.12.1 — even if the global pnpm is something else.

bash
pnpm --version

Output:

text
9.12.1

See pnpm, yarn, and npm for the per-tool deep-dives.

Installing Bun (alternative runtime)

Bun is a Node-compatible JavaScript runtime, package manager, bundler, and test runner — all in one Zig-written binary. Useful as a faster Node replacement for new projects. Coexists with Node; pick per-project via shebang or per-command.

bash
# Linux / macOS / WSL — official installer
curl -fsSL https://bun.sh/install | bash

# macOS — Homebrew
brew install oven-sh/bun/bun

# Windows — PowerShell
powershell -c "irm bun.sh/install.ps1 | iex"

# Verify
bun --version

Output:

text
1.2.14

See bun for the full deep-dive.

Installing Deno (secure runtime)

Deno is a secure-by-default JavaScript and TypeScript runtime by the original creator of Node. Sandboxes scripts behind explicit permission flags. Useful for one-off scripts and security-sensitive workloads.

bash
# Linux / macOS / WSL
curl -fsSL https://deno.land/install.sh | sh

# macOS — Homebrew
brew install deno

# Windows — PowerShell
powershell -c "irm https://deno.land/install.ps1 | iex"

# Cargo (build from source)
cargo install deno --locked

# Verify
deno --version

Output:

text
deno 2.1.4 (stable, release, x86_64-unknown-linux-gnu)
v8 13.0.245.12
typescript 5.6.2

See deno for the full deep-dive.

Choosing between Node, Bun, and Deno

A quick decision table for picking a runtime on a new project.

Project shapeBest pickWhy
Production server, big ecosystemNode 22 LTSMost stable; best library support
Greenfield service, max speedBunFaster install + runtime; Node-compat
One-off script with fetch()DenoZero setup; permission-sandboxed
TypeScript-heavy libraryDeno (publish to JSR)Native TS; no build step
AWS Lambda / Vercel / CloudflareNode 22 or WorkerdBest platform support
Electron appNodeRequired by Electron
Internal tooling, modern teamBunSpeed > compat trade-off acceptable

See bun and deno for the per-runtime details.

Verify the full toolchain

After installing, sanity-check every tool you'll use.

bash
node --version
npm --version
npx --version
corepack --version

Output:

text
v22.14.0
10.9.2
10.9.2
0.31.0

Confirm node_modules/.bin resolution works:

bash
mkdir /tmp/probe && cd /tmp/probe
npm init -y >/dev/null
npm install --save-dev typescript
npx tsc --version

Output:

text
Version 5.5.4

Uninstalling Node

When switching from a system Node to a version-managed setup, remove the old install first. Mixing the two leads to "wrong version" bugs that are hard to diagnose.

Windows

powershell
winget uninstall OpenJS.NodeJS.LTS
# Then delete C:\Users\<You>\AppData\Roaming\npm (global modules)

Output: (none — exits 0 on success)

macOS — Homebrew

bash
brew uninstall node
brew uninstall --ignore-dependencies node
rm -rf /usr/local/lib/node_modules /usr/local/include/node

Output: (none — exits 0 on success)

macOS — official .pkg

bash
sudo rm /usr/local/bin/node /usr/local/bin/npm /usr/local/bin/npx
sudo rm -rf /usr/local/lib/node_modules /usr/local/include/node
sudo rm /etc/paths.d/40-node

Output: (none — exits 0 on success)

Linux — apt

bash
sudo apt-get remove --purge nodejs npm
sudo apt-get autoremove
sudo rm -rf /usr/lib/node_modules

Output: (none — exits 0 on success)

Common pitfalls

  1. EACCES on npm install -g — you're using a system Node and don't have write access to /usr/lib/node_modules. Switch to nvm/fnm/Volta or set prefix=~/.npm-global in ~/.npmrc and add ~/.npm-global/bin to PATH. Never sudo npm install -g.
  2. nvm: command not found after install — the install script edits your shell rc file, but the current shell hasn't sourced it yet. Restart the terminal or source ~/.bashrc.
  3. Wrong Node version in cron / systemd — these don't read your shell rc. Use a full path: /home/alice/.nvm/versions/node/v22.14.0/bin/node.
  4. .nvmrc ignored — nvm only switches on demand (nvm use). Add cd hooks to your shell rc to switch automatically, or use fnm/Volta which auto-switch.
  5. Multiple Node installs fighting on PATHwhich -a node shows them all. Remove duplicates from ~/.bashrc, ~/.profile, /etc/paths.d/, and shell completion scripts.
  6. WSL Node slower than Windows Node — running node in /mnt/c/... is slow because of the 9P filesystem. Keep your project under the Linux home (/home/...) when working in WSL.
  7. corepack enable failing on shared CI — Corepack writes shims to the Node bin directory. On hosted CI with read-only Node, install pnpm/yarn explicitly: npm install -g pnpm@9.
  8. Stale nvm install — old nvm versions don't know about newer LTS lines. Update with nvm install-latest-npm and refresh nvm itself by re-running the install script.
  9. Volta and nvm both installed — Volta's shim and nvm's PATH manipulation collide. Pick one; uninstall the other.
  10. fnm: command not found in new shell — you added the eval to ~/.bashrc, but your terminal opens login shells, which read ~/.profile. Add fnm env --use-on-cd to both or source ~/.bashrc from ~/.profile.

Real-world recipes

Per-project Node version with auto-switch

The recommended setup: a .nvmrc (or .node-version) at the repo root + a shell hook that switches when you cd in. Below uses fnm's built-in --use-on-cd.

bash
# Repo root
echo "22.14.0" > .nvmrc
git add .nvmrc

Output: (none — exits 0 on success)

bash
# Shell rc (one-time)
eval "$(fnm env --use-on-cd)"
bash
cd ~/projects/my-app   # fnm auto-switches to 22.14.0
node --version

Output:

text
Using Node v22.14.0
v22.14.0

CI matrix across Node LTS versions

Test against every supported LTS in parallel. Pin minor versions to keep the matrix stable.

yaml
# .github/workflows/test.yml
jobs:
  test:
    strategy:
      matrix:
        node: [20.18, 22.14, 24.0]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm test
bash
node --version

Output:

text
v22.14.0

Dockerfile with pinned Node + pnpm

Multi-stage build: deps in a cached layer, app in a thin runtime image.

dockerfile
# syntax=docker/dockerfile:1
FROM node:22.14-alpine AS base
RUN corepack enable && corepack prepare pnpm@9.12.1 --activate
WORKDIR /app

FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod

FROM base AS runner
COPY --from=deps /app/node_modules ./node_modules
COPY . .
CMD ["node", "dist/server.js"]
bash
docker build -t my-app .
docker run --rm my-app

Output:

text
Server listening on http://0.0.0.0:3000

Switch between Node 22 and Bun on the same project

Per-shebang selection — Bun for hot-path scripts, Node for the canonical runtime.

text
# bun-only entry (note the shebang)
#!/usr/bin/env bun
console.log(Bun.version);
bash
chmod +x ./script.ts
./script.ts
node script.ts        # uses Node (errors on Bun-only APIs)
bun script.ts         # uses Bun

Output:

text
1.2.14

See bun for what's safe to call.

Bootstrap a clean Mac dev machine

A condensed checklist a teammate can run after a fresh macOS install.

bash
# Xcode CLT (compilers, git)
xcode-select --install

# Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Node via fnm + Corepack-managed pnpm
brew install fnm
echo 'eval "$(fnm env --use-on-cd)"' >> ~/.zshrc
exec zsh
fnm install --lts
corepack enable
corepack prepare pnpm@latest --activate

# Optional: Bun and Deno
brew install oven-sh/bun/bun deno
bash
node --version && pnpm --version && bun --version && deno --version | head -1

Output:

text
v22.14.0
9.12.1
1.2.14
deno 2.1.4 (stable, release, aarch64-apple-darwin)

Pin everything for a team

engines + packageManager + .nvmrc covers every developer regardless of which version manager they use.

json
{
  "engines": {
    "node": ">=22.14 <23",
    "pnpm": ">=9.12"
  },
  "packageManager": "pnpm@9.12.1"
}
text
22.14.0
bash
# After cloning, contributors run:
fnm use            # or nvm use, or asdf install, or mise install
corepack enable
pnpm install

Output: (none — exits 0 on success)

Roll back a Node upgrade

A new Node release broke your project. Switch back without reinstalling everything.

bash
fnm install 22.10.0
fnm use 22.10.0
npm ci

Output:

text
Installed Node v22.10.0
Using Node v22.10.0
added 247 packages in 4s

Pin in .nvmrc so the rollback is shared with the team:

bash
echo "22.10.0" > .nvmrc
git add .nvmrc && git commit -m "pin: roll node back to 22.10.0"

Output: (none — exits 0 on success)