When working on a monorepo with multiple packages, getting everything up and running efficiently can be a challenge. I’ve been there—jumping between terminal windows, manually starting different services, and hoping I didn’t forget anything. Fortunately, NPM workspaces provide a great way to manage multiple packages in a single repository, and with the right setup, starting all of them in development mode can be effortless.
Thank me by sharing on Twitter 🙏
I’ll walk through a few approaches to achieve this, ranging from built-in NPM commands to using turbo
, a high-performance task runner that makes managing workspaces even easier.
Setting Up a Dev Script with NPM Workspaces
NPM has built-in support for workspaces, which means we can run commands across multiple packages from the root of the project. To start, I make sure my package.json
at the root level includes the following:
{
"private": true,
"workspaces": ["packages/*"]
}
This tells NPM that all projects inside the packages/
folder should be treated as part of the workspace.
Next, I add a dev
script to my root package.json
:
Nexus: A Brief History of Information Networks from the Stone Age to AI
$21.66 (as of March 14, 2025 13:37 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Careless People: A Cautionary Tale of Power, Greed, and Lost Idealism
$18.18 (as of March 14, 2025 13:37 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Brother Genuine TN760 High Yield Black Toner Cartridge, (For use with MFC-L2710DW MFC-L2750DW HL-L2350DW HL-L2370DW HL-L2395DW HL-L2390DW DCP-L2550DW Printers)
$79.67 (as of March 14, 2025 13:37 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.){
"scripts": {
"dev": "npm run dev --workspaces --if-present"
}
}
This command runs dev
in all workspace packages that define the script. The --if-present
flag ensures that it won’t fail if some packages don’t have a dev
script.
Each package inside packages/*
should have its own dev
script, like so:
{
"scripts": {
"dev": "next dev"
}
}
Now, when I run:
npm run dev
it starts the dev
script in all workspaces that have one. This is simple and effective but doesn’t allow much flexibility if I need to control execution order or handle dependencies between packages.
Running Scripts in Parallel with npm-run-all
While the built-in NPM approach is useful, sometimes I want more control. That’s where npm-run-all
comes in. First, I install it as a development dependency:
npm install npm-run-all --save-dev
Then, I modify my root package.json
:
{
"scripts": {
"dev": "npm-run-all --parallel dev:package1 dev:package2"
}
}
Since npm-run-all
doesn’t work with --workspaces
, I create scripts that explicitly run dev
for each package:
{
"scripts": {
"dev:package1": "npm --workspace=packages/package1 run dev",
"dev:package2": "npm --workspace=packages/package2 run dev"
}
}
Now, running npm run dev
starts both package1
and package2
in parallel. This approach works well if I need to start only specific workspaces instead of all of them at once.
Using turbo
for Efficient Execution
For larger projects, I prefer using turbo
, which speeds up execution by caching tasks and running only what’s necessary. First, I install turbo
:
npm install turbo --save-dev
Then, I update my root package.json
:
{
"scripts": {
"dev": "turbo run dev"
}
}
To configure turbo
, I create a turbo.json
file in the root of the project:
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"dependsOn": ["^build"],
"outputs": ["build/**", ".vercel/**", "dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"outputs": ["coverage/**"],
"dependsOn": []
},
"lint": {
"dependsOn": ["^build", "^lint"]
},
"check-types": {
"dependsOn": ["^build", "^check-types"]
},
"dev": {
"dependsOn": ["^dev"],
"cache": false,
"persistent": true
}
}
}
This setup ensures that turbo
runs dev
scripts efficiently across all workspaces. Running npm run dev
now starts everything while respecting dependencies and avoiding unnecessary work.
Choosing the Right Approach
Each of these methods works well depending on the needs of the project:
- Built-in NPM Workspaces – Great for simple monorepos where all packages need to start at once.
npm-run-all
– Useful when needing to control which workspaces run in parallel.turbo
– Best for performance optimization, ensuring only necessary tasks run.
By using the right tool for the job, I can make working with multiple packages in a monorepo much smoother. No more juggling terminal windows—just a single command to kick everything off.