Monorepos with npm Workspaces
· 5 min read · Updated April 20, 2026 · intermediate
javascript npm monorepo node packages
Overview
npm workspaces is a feature built into npm 7 and later that lets you manage multiple packages within a single project. Instead of maintaining separate repositories for each library or service, you keep them in one monorepo and npm handles the dependency tree, hoisting shared modules to a single node_modules folder.
The practical benefit is that all your packages share one node_modules directory, installing and updating dependencies is faster, and packages within the monorepo can reference each other by name without publishing to a registry first.
Setting Up Workspaces
The root package.json
Define your workspaces in the root package.json:
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*"
]
}
```bash
The `packages/*` glob matches all subdirectories. You can also list specific paths:
```json
"workspaces": [
"packages/core",
"packages/ui",
"apps/web"
]
```bash
Setting `private: true` prevents the root package from being accidentally published to npm.
### Package structure
With `packages/*`, your directory looks like this:
```bash
my-monorepo/
package.json ← root package with workspaces field
packages/
core/
package.json
ui/
package.json
utils/
package.json
node_modules/ ← shared, hoisted dependencies
```bash
Each workspace has its own `package.json`:
```json
{
"name": "@my-monorepo/core",
"version": "1.0.0",
"main": "dist/index.js"
}
```bash
## Installing and Running
### Installing from root
Run `npm install` from the root — it installs all workspace dependencies in one pass:
```bash
npm install lodash # installs lodash at root (hoisted)
npm install --workspaces # install all workspaces
npm install -w @my-monorepo/core # install to specific workspace
```bash
Dependencies shared across workspaces are hoisted to the root `node_modules`. If `core` and `ui` both depend on `lodash`, npm installs it once at the root.
### Running scripts
Use `-w` to run a script in a specific workspace:
```bash
npm -w @my-monorepo/core run build # build the core package
npm -w @my-monorepo/ui run dev # start the UI dev server
npm run build --workspaces # build all workspaces
```bash
Run a command in every workspace:
```bash
npm run test --workspaces
```bash
## Referencing Workspace Packages
Inside the monorepo, workspace packages reference each other by name just like any other npm package:
```json
{
"name": "@my-monorepo/ui",
"dependencies": {
"@my-monorepo/core": "*"
}
}
```bash
The `*` version means "use whatever is in the monorepo". npm resolves this from the local workspace rather than fetching from the registry. No `npm link` required.
### Depending on the right package
Be explicit about which package you depend on. If you have `@my-monorepo/core` and `@my-monorepo/utils`, make sure each consumer imports from the right one:
```javascript
// In the UI package
import { createStore } from '@my-monorepo/core'; // correct
import { formatDate } from '@my-monorepo/core'; // probably wrong — that's utils
```bash
## Publishing Workspace Packages
When you are ready to publish, use `npm publish` from the root. It automatically publishes packages that have changed:
```bash
npm publish --workspaces --access public
```bash
This publishes each workspace that has a `version` bump. You can also publish from a specific workspace:
```bash
npm -w @my-monorepo/core publish
```json
For private packages, set `"private": false` in the workspace's `package.json` and ensure you have access to publish to your scope on npm.
## Development Workflow
### Watching for changes
When developing locally, you want workspace packages to rebuild when their source changes. With most bundlers you need a watch mode or a tool like `npm run -w` in a loop:
```bash
# In one terminal, watch and rebuild core on changes
npm -w @my-monorepo/core run build -- --watch
# In another terminal, run the UI which depends on core
npm -w @my-monorepo/ui run dev
```bash
More complex setups use Turborepo or changesets to orchestrate builds, cache results, and decide which packages need rebuilding based on what changed.
## Common Use Cases
### Shared utility library
```json
// packages/utils/package.json
{
"name": "@my-monorepo/utils",
"main": "src/index.js"
}
```bash
```javascript
// packages/utils/src/index.js
export function formatCurrency(amount) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount);
}
```bash
```json
// packages/api/package.json
{
"name": "@my-monorepo/api",
"dependencies": {
"@my-monorepo/utils": "*"
}
}
```bash
The API package imports from utils without needing a published version.
### Frontend and backend in one repo
```bash
my-app/
packages/
shared/ # types, constants shared between frontend and backend
server/ # Express/Fastify app
client/ # React/Vue app
package.json
```bash
Both `server` and `client` depend on `shared`. You version bump `shared`, then `server` and `client` update their dependency on it.
## Gotchas
**Circular dependencies.** If package A depends on B and B depends on A, npm will error. Structure your packages in layers — shared utilities at the bottom, applications at the top.
**Version conflicts.** If two workspaces depend on different major versions of the same package, npm cannot hoist them. You get two copies in `node_modules`. This is a symptom of package boundaries being wrong — split or merge packages to reduce dependency overlap.
**The root `node_modules` is shared.** Do not add production dependencies to the root `package.json` unless every workspace needs them. Add dev-only tools there (test runners, linters).
**npm 7+ required.** Workspaces are only available in npm 7 and later. Check your npm version with `npm --version` and upgrade with `npm install -g npm`.
**Node resolution.** When a workspace package imports another by name, Node looks it up in `node_modules` starting from the importer's directory. With workspaces, it finds the hoisted version at the root. This works as expected, but can be confusing when debugging resolution issues.
## See Also
- [/guides/javascript-modules-esm/](/guides/javascript-modules-esm/) — JavaScript module system that npm workspaces builds on
- [/guides/javascript-node-best-practices/](/guides/javascript-node-best-practices/) — Node.js project structure and patterns