Monorepo dependencies with pnpm and npm workspaces
Workspaces let a frontend, an API and shared packages live in one repository with one install. How pnpm and npm workspaces work, and when each one fits.
A frontend, a backend and a shared utility library usually start as three repositories. Soon the shared library is copied by hand, the two apps run different versions of it, and each has its own node_modules with the same packages downloaded twice.
Workspaces solve this by putting the projects in one repository with one install.
How workspaces work
You list the folders that contain packages, and the package manager links them together.
With npm, in the root package.json:
{
"private": true,
"workspaces": ["apps/*", "packages/*"]
}
With pnpm, in pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"
An app then depends on the shared package by name:
{
"dependencies": {
"@acme/utils": "workspace:*"
}
}
That workspace:* form is pnpm's. It guarantees the local package is used, never a copy from the registry. Change the utility library and both apps see it at once.
Running commands
# pnpm
pnpm install
pnpm --filter web dev
pnpm -r build
# npm
npm install
npm run dev --workspace=web
npm run build --workspaces
What pnpm does differently
One copy on disk. pnpm keeps every package version once in a global store and links it into each project. Ten projects that use the same React version share one copy, so installs are fast and disk use stays low.
Strict by default. With npm, a package can import something it never declared, because a dependency of a dependency happens to be installed next to it. This works until the day it does not. pnpm's layout makes such an import fail immediately, so the missing entry in package.json is found during development.
Filtering that understands the graph. pnpm --filter web... build builds the web app and everything it depends on, in the right order.
The two side by side
| npm workspaces | pnpm | |
|---|---|---|
| Setup | Built into npm | One extra tool to install |
| Disk use | Packages copied per repository | One shared store |
| Undeclared imports | Can work by accident | Fail right away |
| Running a subset | --workspace |
--filter, aware of dependencies |
Which one to choose
For two or three packages and a team that wants nothing new to learn, npm workspaces are enough. Beyond that, we reach for pnpm: the strictness prevents a class of bugs, and install times stay short as the repository grows.
Whichever you pick, a workspace does not remove the need for upkeep. Keep the routine from dependency hygiene, and run the audit at the root so it covers every package.