| |

npm 02 🌐 Installing, Updating, and Versioning Node.js & npm

ā±ļø Estimated read: 11 min | |

Node.js and npm are installed together, but they are versioned separately. Node.js ships with a specific npm version bundled into each release—Node.js 24.19.0 includes npm 11.17.0, for example—and npm releases new versions more frequently than Node.js does . This means the npm you have is not necessarily the latest npm, even if your Node.js is current. The official guidance is to use a Node version manager rather than a system installer, because the installer places npm in a directory with local permissions that cause errors when installing global packages . The version manager installs Node.js and npm in the user’s home directory, sidesteps the permission problem, and makes switching between versions trivial.

The versioning story is the other half of the topic. Node.js follows a release schedule that determines which versions are stable enough for production, and npm follows semantic versioning, which determines what a version range like ^4.18.0 actually means. A production application should run on an Active LTS or Maintenance LTS release, not on the Current line . The version range in package.json is a contract about which updates are acceptable, and understanding the caret and tilde is the difference between a predictable dependency tree and one that breaks on a Tuesday because a transitive dependency published a major version.

Key point: Use a Node version manager (nvm on macOS/Linux, nvm-windows on Windows) to install and switch Node.js versions. Node.js 27+ moves to one major release per year, with every release becoming LTS after six months of Current and thirty months of LTS support. npm install -g npm@latest updates npm independently of Node.js. The caret (^) allows minor and patch updates, the tilde (~) allows only patch updates, and exact versions pin the dependency.


Why the version manager is the recommended install method

The permission problem. A system installer puts Node.js and npm in a directory like /usr/local/lib/node_modules, which is owned by root. A global install (npm install -g) then tries to write to that directory as the unprivileged user, and it fails with EACCES . The workaround is sudo npm install -g, which is dangerous because the package’s install scripts run as root. The version manager avoids the problem entirely by installing into ~/.nvm/versions/node/<version>/, which the user owns.

The multi-version problem. Different projects require different Node.js versions. One application might be pinned to Node.js 18 because a dependency has not been updated, and another might require Node.js 22 for a newer API . A system installer installs exactly one version. The version manager installs as many as needed and switches between them per shell, per project, or per directory.

The reproducibility problem. A project can record its required Node.js version in a .nvmrc file. nvm use reads the file and switches to that version, and nvm install installs it if it is missing . This is the Node.js equivalent of the lockfile: the project declares its runtime, and the tooling enforces it.


a. Installing and switching Node.js with nvm

The installation is a script that appends initialization code to the shell profile. On macOS and Linux, the script is fetched from the nvm repository and run with bash . The command -v nvm check verifies that the shell has reloaded the profile and that nvm is available.

# Download and run the nvm install script
# The script writes to ~/.nvm and adds initialization to the shell profile
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash

# Reload the shell profile so nvm is available
# Use ~/.zshrc for zsh, ~/.bashrc for bash
source ~/.bashrc

# Verify nvm is installed
nvm --version

# Install the latest LTS version of Node.js
nvm install --lts

# Install a specific version
nvm install 20.11.0

# List installed versions
nvm ls

# List available remote versions
nvm ls-remote

# Switch to a specific version
nvm use 20

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

The nvm install --lts command installs the latest Long-Term Support release. The nvm use command switches the current shell to the specified version by rewriting the node symlink in the nvm directory . The nvm alias default command sets the version that new shells start with, which is the one to set after installing the version the project should use by default.

The .nvmrc file is the per-project mechanism. Create it in the project root with the version number, and nvm use in that directory switches automatically.

# Write the required version to .nvmrc
echo "20.11.0" > .nvmrc

# In the project directory, switch to the version
nvm use

# Or install it if it is not present, then use it
nvm install

The nvm use command with no argument reads the .nvmrc in the current directory. The shell can be configured to run this automatically on directory change, which is the pattern for a project that must run on a specific version .

On Windows, nvm does not support the native shell. The nvm-windows package is the equivalent, and it is installed from its GitHub releases rather than the shell script .


b. Updating npm and the Node.js release schedule

npm is bundled with Node.js, but it can be updated independently. The command is npm install -g npm@latest, and it installs the latest npm into the Node.js version’s node_modules directory . The npm -v command verifies the result.

# Update npm to the latest version
# The -g flag installs it into the active Node.js version's global directory
npm install -g npm@latest

# Verify the version
npm -v

The update does not affect the Node.js version. It replaces the npm that ships with that Node.js version with the latest npm, and the next nvm use to a different Node.js version reverts to that version’s bundled npm until it is updated separately. The nvm install node --reinstall-packages-from=node command is the nvm feature that installs a new Node.js version and copies the global packages from the previous version in one step .

The Node.js release schedule determines which versions are safe for production. Until Node.js 26, odd-numbered releases were Current for six months and then unsupported, and even-numbered releases moved to Active LTS . Starting with Node.js 27, the schedule changes: one major release per year in April, an LTS promotion in October, and every release becomes LTS . The LTS phase lasts thirty months, and production applications should use only Active LTS or Maintenance LTS releases . The Alpha channel replaces the early-testing role that odd-numbered releases served, and it allows semver-major changes during the Alpha phase .

PhaseDurationMeaning
Alpha6 monthsEarly testing, breaking changes allowed
Current6 monthsStabilization, not for production
LTS30 monthsProduction-ready, critical fixes
EOL—No support

c. Semantic versioning and the range syntax

npm uses semantic versioning, which divides version numbers into three parts: major, minor, and patch. The major number changes when a release breaks backward compatibility, the minor number changes when a release adds a backward-compatible feature, and the patch number changes when a release fixes a bug without changing the API . The version range in package.json declares which of those changes the project accepts.

The caret (^) is the default when npm install --save is used. It allows minor and patch updates but not major updates. ^1.2.3 matches 1.2.3 and every version up to but not including 2.0.0 . The one exception is the 0.x range: ^0.1.2 matches 0.1.2 and every version up to but not including 0.2.0, because the 0.x major number is treated as unstable .

The tilde (~) is more restrictive. It allows patch updates but not minor updates. ~1.2.3 matches 1.2.3 and every version up to but not including 1.3.0 .

// The version ranges in package.json
{
  "dependencies": {
    // Caret: minor and patch updates allowed
    "express": "^4.18.0",
    // Tilde: patch updates only
    "lodash": "~4.17.21",
    // Exact: no updates allowed
    "react": "18.2.0",
    // Wildcard: any version
    "some-lib": "*"
  }
}

The exact version has no prefix. "react": "18.2.0" installs exactly that version and nothing else, which is the most predictable but requires manual updates. The wildcard * accepts any version, which is the least predictable and should be avoided in application code.

The lockfile is the other half of the versioning story. package-lock.json records the exact version and resolved location of every package in the tree, and npm install uses it to recreate the identical tree. The ranges in package.json are the constraints; the lockfile is the resolution. npm ci installs exactly what the lockfile specifies, fails if the two disagree, and is the command for CI pipelines .


Complete Example Session

The following session installs nvm, installs two Node.js versions, switches between them, updates npm, and demonstrates the range syntax.

# ============================================
# PART 1: INSTALL NVM
# ============================================
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
source ~/.bashrc
nvm --version

# ============================================
# PART 2: INSTALL NODE.JS VERSIONS
# ============================================
# Install the latest LTS version
nvm install --lts

# Install a specific older version
nvm install 18.20.0

# List installed versions
nvm ls

# ============================================
# PART 3: SWITCH BETWEEN VERSIONS
# ============================================
# Switch to Node.js 18
nvm use 18.20.0
node -v
# Output: v18.20.0

# Switch back to the LTS version
nvm use --lts
node -v
# Output: v22.x.x (or whatever the LTS is)

# Set the default for new shells
nvm alias default 22

# ============================================
# PART 4: UPDATE NPM
# ============================================
npm install -g npm@latest
npm -v

# ============================================
# PART 5: VERSION RANGES
# ============================================
# Create a package.json with different ranges
cat > package.json << 'EOF'
{
  "dependencies": {
    "express": "^4.18.0",
    "lodash": "~4.17.21",
    "react": "18.2.0"
  }
}
EOF

# Install the dependencies
npm install

# Check the resolved versions in the lockfile
grep -A2 '"express"' package-lock.json | head -5

The five parts cover the nvm installation, the two Node.js versions, the switch, the npm update, and the version range demonstration. What the session shows is that the version manager makes the Node.js version a per-shell choice, and the range syntax determines what npm install will accept.


Quick Reference

Version Managers

ToolPlatformInstall
nvmmacOS, Linux, WSLShell script from GitHub
nvm-windowsWindowsGitHub releases
fnmCross-platformPackage manager

nvm Commands

CommandEffect
nvm install --ltsInstall latest LTS
nvm install 20Install major version 20
nvm use 20Switch to 20
nvm alias default 20Set default
nvm lsList installed
nvm ls-remoteList available

npm Update

CommandEffect
npm install -g npm@latestLatest npm
npm install -g npm@10Specific major
npm -vVerify

Version Ranges

RangeMatches
^1.2.3>=1.2.3 <2.0.0
~1.2.3>=1.2.3 <1.3.0
1.2.3Exactly 1.2.3
*Any version

Best Practices

āœ… Do This:

# Use nvm, not the system installer
nvm install --lts

# Pin the project version in .nvmrc
echo "20.11.0" > .nvmrc

# Use the caret for application dependencies
"express": "^4.18.0"

# Use npm ci in CI
npm ci

āŒ Don’t Do This:

# Use sudo with npm install -g
sudo npm install -g some-package # root-owned files

# Use * for application dependencies
"express": "*" # unpredictable

# Ignore the lockfile
rm package-lock.json # breaks reproducibility

Common Pitfalls

PitfallWhy It HappensFix
EACCES on global installSystem installer put npm in root-owned dirUse nvm
Wrong Node.js versionForgot nvm use.nvmrc + nvm use
npm version revertsSwitched Node.js versionUpdate npm per version
Range resolves to unexpected versionCaret semanticsPin exact or use ~
CI install differsNo npm ciUse npm ci

Real-World Examples

1. Install LTS

nvm install --lts

2. Switch version

nvm use 20

3. Pin with .nvmrc

echo "20.11.0" > .nvmrc

4. Update npm

npm install -g npm@latest

5. Caret range

"express": "^4.18.0"

6. Tilde range

"lodash": "~4.17.21"

7. Exact version

"react": "18.2.0"

8. Reinstall global packages

nvm install node --reinstall-packages-from=node

9. CI install

npm ci

10. Check versions

node -v && npm -v

Visual

nvm Version Switching

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  ~/.nvm/versions/node/                       │
│  ā”œā”€ā”€ v18.20.0/                               │
│  ā”œā”€ā”€ v20.11.0/                               │
│  └── v22.x.x/                                │
│         │                                    │
│         ā–¼                                    │
│  nvm use 20                                  │
│  → symlink node → v20.11.0/bin/node          │
│  → npm → v20.11.0/bin/npm                    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Release Schedule

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Node.js 27+                                 │
│  Alpha: Oct–Mar (6 months)                   │
│  Current: Apr–Oct (6 months)                 │
│  LTS: Oct + 30 months                        │
│  EOL: no support                             │
│                                              │
│  Production: Active LTS or Maintenance LTS   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Version Range

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  ^1.2.3 → >=1.2.3 <2.0.0                     │
│  ~1.2.3 → >=1.2.3 <1.3.0                     │
│  1.2.3  → exactly 1.2.3                      │
│  *      → any version                        │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Summary

ItemValue
Recommended installerNode version manager
macOS/Linux managernvm
Windows managernvm-windows
Default version file.nvmrc
npm updatenpm install -g npm@latest
Caret range^1.2.3 → minor + patch
Tilde range~1.2.3 → patch only
LTS duration30 months
Production versionsActive LTS or Maintenance LTS
CI installnpm ci

Key takeaways:

  • Use a version manager, not a system installer. The installer puts npm in a root-owned directory, and global installs fail with EACCES. The version manager installs into the user’s home directory and makes switching versions trivial .
  • Node.js 27+ moves to one major release per year. Every release becomes LTS after six months of Current and thirty months of LTS support. Production should use Active LTS or Maintenance LTS .
  • npm is updated independently of Node.js. npm install -g npm@latest installs the latest npm into the active Node.js version’s directory .
  • The caret allows minor and patch updates; the tilde allows only patch updates. The caret is the default for npm install --save, and the 0.x range has a special behavior where the caret only allows patch updates .
  • The lockfile is the resolution of the ranges. package.json declares the constraints, package-lock.json records the exact tree, and npm ci installs the lockfile exactly .

Remember: The Node.js and npm versions are two separate decisions. The Node.js version is the runtime, and it is chosen by the version manager and pinned in .nvmrc. The npm version is the package manager, and it can be updated independently. The version ranges in package.json declare which package updates the project accepts, and the lockfile records the exact versions that were installed. Use nvm, pin the runtime, update npm deliberately, and let the lockfile handle the dependency tree.



Stop using slow, ad-bloated tool sites! 🤮

šŸ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
āœ… Finance (Mortgage, Interest, Inflation)
āœ… Tech (Base64, JSON, Dev Suite, IP)
āœ… Health (BMI, BMR, TDEE)
āœ… Productivity (Timer, Workspace, QR)

āš”ļø Fast & Private
šŸ”’ No data leaves your device
šŸ’Ž 100% Free

šŸ”— Use it now: https://tools.kandz.me
šŸ”– Bookmark it—you’ll need it later!