Guide to npm Workspaces for JavaScript Monorepos

npm workspaces provide a native solution within the Node Package Manager to manage multiple packages inside a single top-level repository, commonly known as a monorepo. This article explains what npm workspaces are, their core mechanisms for handling shared dependencies and internal package linking, and how to configure and execute commands across projects efficiently without relying on third-party tools.

What Are npm Workspaces?

Introduced in npm v7, workspaces are a set of features that allow developers to manage multiple packages within a single root package. Instead of maintaining isolated repositories for individual modules, components, or services, teams can house all related projects inside a single repository while keeping their individual package definitions intact.

This setup eliminates the overhead of publishing separate packages to a registry just to share code between internal projects during development.

How npm Workspaces Manage Monorepos

npm workspaces streamline monorepo workflows through three primary mechanisms: dependency hoisting, automatic symlinking, and coordinated command execution.

1. Dependency Hoisting

In a multi-package environment without workspaces, every sub-project maintains its own node_modules directory, leading to duplicate dependencies and massive disk usage. npm workspaces solve this by hoisting shared dependencies to a single root-level node_modules folder. The root package-lock.json file locks the versions for the entire repository, ensuring consistent builds across all packages.

2. Automatic Symlinking

When a package inside the workspace depends on another package within the same repository, npm automatically creates a symlink in the root node_modules. If package A declares package B as a dependency in its package.json, npm resolves B locally rather than fetching it from the npm registry. Changes made to package B are immediately reflected in package A without manual linking steps.

3. Centralized Script Execution

Workspaces allow you to run npm lifecycle scripts (such as test, build, or lint) across all sub-packages simultaneously or target specific packages directly from the root directory using CLI flags.

Setting Up an npm Workspace

To initialize a monorepo with npm workspaces, define the workspaces property in the root package.json file.

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

The root project is typically set to "private": true to prevent accidental publication to the npm registry. The workspaces array uses glob patterns to specify the directories that contain individual packages.

A standard directory structure looks like this:

my-monorepo/
├── package.json
├── package-lock.json
├── packages/
│   ├── ui-components/
│   │   └── package.json
│   └── utils/
│       └── package.json
└── apps/
    └── web-app/
        └── package.json

Common Workspace Commands

Managing dependencies and executing tasks is handled through standard npm commands paired with workspace flags:

By integrating these features directly into the default Node.js toolchain, npm workspaces eliminate the complexity of configuring external monorepo tools for projects that only require standardized dependency management and local package linking.