Skip to content
Node.js nd modules 4 min read

npm Basics: Installing & Managing Packages

npm is the default package manager that ships with every Node.js installation, and it is the tool you use to pull third-party code into your project, keep it up to date, and remove it when it is no longer needed. Almost every non-trivial Node application depends on packages from the npm registry, so understanding how npm install works — and what it does to your node_modules folder and package.json — is foundational. This page covers installing, updating, and uninstalling packages, the difference between local and global installs, and how the dependency tree is laid out on disk.

Installing packages

The core command is npm install (aliased as npm i). Running it with a package name fetches that package from the registry, writes it into node_modules, and records it in your package.json. Running it with no arguments installs everything already listed in package.json — this is what you do after cloning a repository.

# Install a single package and add it to dependencies
npm install express

# Shorthand
npm i express

# Install everything declared in package.json (e.g. after git clone)
npm install

# Install a specific version
npm install [email protected]

After installation, npm updates the dependencies section of package.json and writes an exact, reproducible tree into package-lock.json.

Output:

added 65 packages, and audited 66 packages in 2s

10 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Local vs global installs

By default, packages install locally — into the node_modules folder of the current project. Local packages are what your application’s import and require statements resolve against, and they are scoped to that one project. This is what you want for runtime dependencies and libraries.

A global install (-g) places a package in a system-wide location and is intended for command-line tools you want available from any directory, such as npm-distributed CLIs.

# Local — used by this project's code
npm install lodash

# Global — installs a CLI binary onto your PATH
npm install -g http-server
AspectLocal (default)Global (-g)
LocationProject node_modulesSystem-wide prefix
Recorded in package.jsonYesNo
Used by import/requireYesNo
Typical useLibraries, frameworksStandalone CLI tools

Prefer a local install plus npx over a global install for project tooling. npx eslint runs the version pinned in your project, keeping every contributor on the same tool version instead of relying on whatever each machine installed globally.

Dependencies vs devDependencies

Packages your application needs at runtime belong in dependencies. Packages needed only during development — test runners, bundlers, type definitions, linters — belong in devDependencies, declared with --save-dev (-D). This separation matters because production installs can skip devDependencies entirely.

# Runtime dependency
npm install zod

# Development-only dependency
npm install --save-dev vitest

# Shorthand
npm i -D typescript

Installing with --omit=dev (the modern replacement for --production) installs only the dependencies, which is exactly what you want in a production container build:

npm install --omit=dev

Updating packages

Dependencies in package.json use semantic-version ranges (e.g. ^4.19.0), so they are allowed to move within a compatible band. npm outdated shows you which packages have newer versions available, and npm update upgrades them within the range your package.json permits.

# See what's behind
npm outdated

# Update packages to the newest version allowed by your ranges
npm update

# Update a single package
npm update express

Output:

Package  Current  Wanted  Latest  Location          Depended by
express   4.18.2  4.19.2  5.1.0   node_modules/...  my-app
zod        3.22.4  3.23.8  3.23.8  node_modules/...  my-app

The Wanted column is the highest version satisfying your range (what npm update installs); Latest is the absolute newest published version. Jumping to a new major (Latest when it exceeds Wanted) requires changing the range yourself — npm install express@5 — because major versions may contain breaking changes.

Removing packages

npm uninstall (aliased npm remove, npm rm) deletes a package from node_modules and removes its entry from package.json and package-lock.json.

npm uninstall lodash

# Remove a global tool
npm uninstall -g http-server

How node_modules and the dependency tree work

When you install a package, npm also installs everything that package depends on, and their dependencies, and so on — the full transitive tree. Rather than nesting every dependency inside its parent, modern npm flattens (hoists) packages to the top level of node_modules whenever versions are compatible. This deduplicates shared dependencies and shortens paths.

node_modules/
├── express/
├── accepts/          ← hoisted dependency of express
├── body-parser/      ← hoisted dependency of express
└── debug/            ← shared by several packages, installed once

When two packages need incompatible versions of the same dependency, npm keeps the conflicting copy nested inside the package that requires it, so both versions coexist safely. The resolver’s node_modules walk-up (covered in Module Resolution) is what makes both the hoisted and nested copies findable.

Never commit node_modules to version control, and never edit files inside it. It is a generated, machine-specific artifact. Commit package.json and package-lock.json instead, and let npm install (or npm ci in CI) rebuild the tree reproducibly.

Best practices

  • Commit package.json and package-lock.json; add node_modules/ to .gitignore.
  • Use --save-dev for tooling so production installs (--omit=dev) stay lean.
  • Run npm ci instead of npm install in CI and Docker builds for exact, lockfile-driven installs.
  • Review npm outdated periodically and update inside your semver ranges with npm update; bump majors deliberately and test.
  • Run npm audit after installs to surface known vulnerabilities, and pin exact versions for tooling where reproducibility matters most.
  • Prefer local installs with npx over global installs to keep tool versions consistent across machines.
Last updated June 14, 2026
Was this helpful?