learn.colinkim.dev

npm and pnpm: workspaces and monorepos

Learn how workspaces help manage multiple related projects and why pnpm is especially efficient for monorepos.

As projects grow, developers often split them into multiple packages. A design system might have component packages. A large app might have shared libraries. Managing dependencies across these packages becomes complex. Workspaces solve this problem.

The problem workspaces solve

Imagine you have three packages:

my-project/
  shared-utils/
    package.json (needs lodash)
  ui-components/
    package.json (needs shared-utils and react)
  main-app/
    package.json (needs shared-utils, ui-components, and express)

Without workspaces, each package manages dependencies independently:

  • shared-utils installs lodash
  • ui-components installs react and a separate copy of shared-utils
  • main-app installs express and separate copies of shared-utils and ui-components

Each package has its own node_modules. Dependencies get duplicated across packages.

Workspaces let you manage these packages together, sharing dependencies where possible.

Basic workspace configuration

At the root of your project, create a pnpm-workspace.yaml file (for pnpm):

packages:
  - 'shared-utils'
  - 'ui-components'
  - 'main-app'

Or in package.json (for npm):

{
  "workspaces": [
    "shared-utils",
    "ui-components",
    "main-app"
  ]
}

This tells the package manager which directories are part of the workspace.

How workspaces share dependencies

With workspaces enabled, install dependencies from the root:

pnpm install

The package manager:

  • Installs all dependencies for all workspace packages
  • Shares common dependencies across packages
  • Creates a single node_modules at the root when possible
  • Links workspace packages to each other

If both ui-components and main-app need react, they share the same installation instead of duplicating it.

Linking workspace packages

In your workspace packages, you can reference other workspace packages using the workspace: protocol:

{
  "dependencies": {
    "shared-utils": "workspace:*",
    "react": "^18.0.0"
  }
}

The workspace:* means “use the version from this workspace.” When you install, pnpm links the packages together instead of downloading from the registry.

Running commands in specific workspaces

pnpm lets you run commands in specific workspace packages using filters:

pnpm --filter ui-components build

This runs pnpm build only in the ui-components package.

To run a command in all packages:

pnpm -r build

The -r flag means “recursive” — run this command in every workspace package.

To run in a subset of packages:

pnpm --filter "./packages/*" test

This runs tests in all packages matching the packages/* pattern.

Filters let you focus on specific packages. During daily work, you can build, test, and develop packages independently while still sharing dependencies.

Why pnpm excels at workspaces

pnpm’s content-addressable storage makes it especially efficient for workspaces:

  • Single store for all dependencies across all packages
  • Hard links from each package to the shared store
  • Minimal duplication even when packages use different versions
  • Fast installs because files are linked, not copied

In a large monorepo with many packages sharing dependencies, pnpm can use significantly less disk space than npm.

Workspace scripts

You can define scripts that operate on the workspace:

{
  "scripts": {
    "build:all": "pnpm -r build",
    "test:all": "pnpm -r test",
    "dev:ui": "pnpm --filter ui-components dev"
  }
}

Then run from the root:

pnpm build:all

When to use workspaces

Use workspaces when:

  • Multiple packages share dependencies
  • You’re building shared libraries alongside an app
  • You want to develop related packages together
  • You need to coordinate releases across packages

For a single package, workspaces add complexity without benefit. For multiple related packages, workspaces reduce duplication and make coordination easier.

Don’t reach for workspaces immediately. Build your first package. When you need to split it into multiple coordinated packages, then add workspace configuration.

Common workspace patterns

Design system:

packages/
  design-tokens/
  icons/
  ui-components/
  main-app/

Shared libraries:

packages/
  api-client/
  shared-types/
  admin-app/
  user-app/

Micro-frontend:

packages/
  shell-app/
  product-page/
  checkout/
  profile/

Workspaces reduce duplication and keep related code organized while maintaining clear boundaries between packages.

Quick Check

One answer

When do workspaces usually become worth the added complexity?

Choose the best answer and use it to track your progress through the lesson.

Progress

Quick checks

No quick checks in this lesson.

Mark lesson manually or answer quick checks to track progress.