Vikram D.

npm, pnpm, and Yarn: A Practical Guide to JavaScript Package Managers

2025-12-0515 min read
npm, pnpm, and Yarn: A Practical Guide to JavaScript Package Managers

Modern JavaScript development is unthinkable without package managers. React, TypeScript, ESLint, Webpack, Vite, Jest, Cypress — almost everything you use comes from npm packages.

But there isn’t just one way to manage them.

Today, the three most common tools are:

  • npm — the default, ships with Node.js
  • Yarn — introduced faster installs and better workflows
  • pnpm — focuses on speed and disk efficiency with a clever storage model

In this post, we’ll walk through:

  • What a package manager actually does
  • The core concepts that npm, pnpm, and Yarn share
  • How each tool behaves, with concrete examples
  • Strengths and trade-offs of each
  • How to pick one for your projects and monorepos

1. What Does a Package Manager Do?

At a high level, a package manager:

  • Installs dependencies listed in your package.json
  • Resolves and pins exact versions in a lockfile
  • Creates and manages the node_modules folder
  • Runs scripts defined in package.json (lint, test, build, etc.)
  • Publishes packages to registries like npmjs.com

A minimal package.json might look like:

{
  "name": "my-app",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vitest"
  },
  "dependencies": {
    "react": "^18.3.0",
    "react-dom": "^18.3.0"
  },
  "devDependencies": {
    "vite": "^5.0.0",
    "vitest": "^2.0.0"
  }
}

All three package managers understand this file. The differences lie in:

  • Install speed
  • Disk space usage
  • Lockfile format
  • Workspace/monorepo support
  • Extra features and workflows

2. Core Concepts: Same Ideas, Different Implementations

Before we dive into each tool, let’s align on concepts they all share.

2.1 Dependencies and Version Ranges

In package.json:

"dependencies": {
  "axios": "^1.7.0",
  "zustand": "~5.0.0"
}

Common version range operators:

  • ^1.7.0

    • Accepts >=1.7.0 <2.0.0
    • Allows new features and bug fixes, but blocks breaking changes
  • ~5.0.0

    • Accepts >=5.0.0 <5.1.0
    • Only patch updates; more conservative

Exact versions:

"typescript": "5.6.3"

pin a specific version. The lockfile is what makes installs reproducible across machines and CI.

2.2 Lockfiles

Each tool has its own lockfile format:

  • npm: package-lock.json
  • Yarn (Classic): yarn.lock
  • Yarn Berry (v2+): still yarn.lock, but different format
  • pnpm: pnpm-lock.yaml

Lockfiles store exact versions and integrity hashes so that:

  • Local dev, CI pipelines, and production builds all resolve to the same versions
  • Installs become faster, since resolution work is cached

Rule of thumb:

Commit your lockfile to Git for apps. For libraries, lockfile strategy can vary, but you almost always commit it for the primary repo.

2.3 Scripts

All of them support npm run style scripts:

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "test": "vitest run",
  "lint": "eslint src --ext .ts,.tsx"
}

Commands:

  • npm: npm run dev
  • pnpm: pnpm dev (shorthand for pnpm run dev)
  • Yarn: yarn dev

They all inject node_modules/.bin into the PATH, so you can run tools without global installs.


3. npm: The Default Package Manager

3.1 What Is npm?

npm is:

  • The default package manager installed with Node.js
  • The name of the client (npm CLI)
  • Also the name of the registry (https://registry.npmjs.org)

If you install Node, you get npm automatically:

node -v
npm -v

3.2 Basic npm Workflow

Initialize a project:

npm init -y

Install dependencies:

# Add a regular dependency
npm install react react-dom

# Add a dev dependency
npm install --save-dev typescript vite

This will:

  • Update dependencies or devDependencies in package.json
  • Create or update package-lock.json
  • Populate node_modules/ with installed packages

Run scripts:

npm run dev
npm run build
npm test

Use npx (or npm exec) for one-off commands:

npx create-next-app my-app
npx vitest

3.3 Pros of npm

  • Comes with Node; zero extra setup
  • The default ecosystem target (everything is tested with npm)
  • Actively improved; performance and features have gotten much better in recent versions
  • npm workspaces are now a built-in way to manage monorepos

Example of npm workspaces:

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"]
}

Then:

npm install
npm run build --workspace apps/web

3.4 Cons / Limitations

Historically (especially older versions):

  • Slower installs compared to pnpm and Yarn
  • node_modules layout can be very large and deeply nested
  • Earlier versions had more issues with deterministic installs

Many of these have improved, but npm is still not the most disk-efficient option.


4. Yarn: Faster Installs and Better Workflows

4.1 Why Yarn Was Created

Yarn was originally created by Facebook (and others) to address issues they saw with npm at the time:

  • Slow and sometimes unreliable installs
  • Non-deterministic dependency resolution
  • Problems working on very large codebases

Yarn introduced:

  • A different lockfile format (yarn.lock)
  • Parallelized downloads and caching for speed
  • A focus on deterministic installs

4.2 Yarn Classic vs Yarn Berry

There are two “families” of Yarn:

  • Yarn Classic (v1)

    • Very popular, especially in older projects
    • Uses node_modules like npm
    • Great support for workspaces
  • Yarn Berry (v2+)

    • Completely redesigned
    • Supports Plug’n’Play (PnP) — no node_modules at all
    • More opinionated and config-heavy
    • Very powerful, but can surprise newcomers

This post will mostly treat them together at a high level and mention differences where relevant.

4.3 Basic Yarn Workflow

Initialize:

yarn init -y

Install dependencies:

# Add a regular dependency
yarn add react react-dom

# Add a dev dependency
yarn add --dev typescript vite

Run scripts:

yarn dev
yarn build
yarn test

Install all dependencies (after cloning a repo):

yarn install

4.4 Yarn Workspaces

Yarn popularized the concept of workspaces for monorepos.

In your root package.json:

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"]
}

Now:

  • apps/web can depend on @my-scope/ui from packages/ui
  • Yarn links them locally (no publishing required)
  • yarn install deduplicates dependencies across all workspaces

Workspaces are extremely useful for:

  • Shared components (packages/ui)
  • Utility libraries (packages/utils)
  • Config packages (packages/eslint-config, packages/tsconfig)

4.5 Plug’n’Play (Yarn Berry Feature)

Yarn Berry introduced Plug’n’Play (PnP):

  • No node_modules folder
  • Yarn creates a single zip archive for dependencies and a .pnp.cjs file
  • Modules are resolved via the PnP API instead of filesystem crawling

Pros:

  • Faster installs and smaller disk usage
  • Catches non-declared dependencies (you must declare them)

Cons:

  • Some tools assume node_modules exists and need adapters
  • Slightly steeper learning curve

Many teams stay on Yarn Classic or use Berry without PnP if they prefer a smoother transition.


5. pnpm: Performance and Disk Efficiency

5.1 What Makes pnpm Different?

pnpm was created to solve two big problems:

  1. Deeply nested node_modules folders wasting a lot of disk space
  2. Slow, repeated downloads of the same packages across projects

Its core idea:

Use a content-addressable store and symlinks instead of duplicating packages in each project.

In plain English:

  • Dependencies are stored once in a global store (per machine)
  • Each project’s node_modules folder points to that store via symlinks
  • You can have dozens of projects using React, but React’s files only live on disk once

5.2 Basic pnpm Workflow

Install pnpm (one-time):

npm install -g pnpm

Initialize:

pnpm init

Install dependencies:

# Regular dependency
pnpm add react react-dom

# Dev dependency
pnpm add -D typescript vite

Run scripts:

pnpm dev
pnpm build
pnpm test

Install all dependencies:

pnpm install

5.3 pnpm’s Node Modules Layout

pnpm creates a special node_modules structure:

  • Each package is a symlink to the global store
  • The actual files live in something like ~/.pnpm-store
  • Node’s module resolution still works, but it enforces stricter rules

Benefits:

  • Massive disk space savings, especially in monorepos
  • Faster installs once packages are cached
  • Helps catch bad patterns like accessing undeclared dependencies

5.4 pnpm Workspaces

pnpm has first-class support for workspaces via pnpm-workspace.yaml:

packages:
  - 'apps/*'
  - 'packages/*'

Then:

pnpm install
pnpm -r test      # run test in all packages (recursive)
pnpm -r build

The -r (recursive) flag is extremely handy in monorepos.

5.5 Why Many Monorepos Prefer pnpm

You’ll often see pnpm used in larger monorepos because:

  • It uses far less disk space
  • Workspace support is solid and simple
  • It enforces better dependency hygiene
  • It keeps install times very competitive

For a big repo with many packages and branches, these benefits add up quickly.


6. Comparing npm, pnpm, and Yarn

Here’s a simplified overview:

Feature / AspectnpmYarnpnpm
Default with Node.js✅ Yes❌ No❌ No
Lockfilepackage-lock.jsonyarn.lockpnpm-lock.yaml
Disk usageHighMedium (Classic), Low (PnP)Low (shared store + symlinks)
Speed (cold install)Good (recent versions)GoodVery good
Speed (warm install)GoodVery goodVery good
Workspaces support✅ Yes (built-in)✅ Yes✅ Yes
Global storeBasic cacheCacheContent-addressable store
Node.js ecosystem fitExcellent (default assumption)ExcellentExcellent
PnP (no node_modules)❌ No✅ Yarn Berry❌ No
Learning curveEasiestModerate (Berry more complex)Moderate (due to store and layout)

All three are good choices in 2025. The question is more about fit than “which one is objectively best.”


7. Choosing the Right Package Manager

Here are some practical guidelines.

7.1 When npm Is a Great Choice

Use npm if:

  • You prefer simplicity and minimal tooling decisions
  • You’re working on small to medium apps
  • Your team is new to Node/JS tooling
  • You want to avoid explaining extra global tools

Recent npm versions support:

  • Workspaces
  • Lockfiles
  • Good performance

For many apps, npm is more than enough.

7.2 When Yarn Makes Sense

Use Yarn if:

  • Your team is familiar with Yarn from past projects
  • You’re using React tooling that assumes Yarn (some older templates do)
  • You want workspaces, and your organization already standardized on Yarn
  • You’re ready to invest in Yarn Berry and possibly PnP for stricter dependency management

Yarn is tried and tested in very large codebases (think Meta, etc.).

7.3 When pnpm Shines

Use pnpm if:

  • You’re building a monorepo with many packages
  • Disk usage and install performance matter a lot (e.g., CI, many branches)
  • You want stricter dependency resolution to catch bad imports
  • You like the idea of a shared, content-addressable store

pnpm is especially attractive for:

  • Design system repos (packages/ui, packages/tokens, etc.)
  • Backend + frontend apps sharing common utilities
  • Multi-app setups with shared libraries

8. Migrating Between Package Managers

You can often switch between them, but do it cleanly:

  1. Delete node_modules and the old lockfile(s)
  2. Install using the new tool
  3. Commit the new lockfile

Example: migrate from npm to pnpm

rm -rf node_modules package-lock.json
pnpm install

It’s best to:

  • Pick one per repo
  • Document it in your README
  • Avoid switching back and forth, or mixing tools (that leads to confusion and broken lockfiles)

9. Summary

npm, pnpm, and Yarn all solve the same core problem: managing JavaScript dependencies. But they do it with different trade-offs:

  • npm: simple, stable, and ships with Node. Great default for many apps.
  • Yarn: introduced faster, deterministic workflows, excellent workspace support, and advanced features in Berry.
  • pnpm: aggressively efficient with disk and installs, ideal for monorepos and large codebases.

If you’re starting a new project today:

  • For a single app or small project: npm or pnpm
  • For a monorepo with multiple apps and packages: pnpm or Yarn workspaces
  • For teams already standardized on something: stick with what your team knows, unless you have a strong reason to switch

Ultimately, the “best” package manager is the one your team understands and uses consistently, backed by good practices:

  • Committed lockfiles
  • Clear scripts
  • Consistent workflow in local dev and CI

Pick one, learn it well, and let it disappear into the background while you focus on building great products.

Loading comments...