Turborepo for Node
Turborepo orchestrates workspace tasks with remote caching so unchanged packages skip rebuilds in CI and locally.
Recipe
Quick-reference recipe card - copy-paste ready.
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"typecheck": {
"dependsOn": ["^build"],
"outputs": []
}
}
}npx turbo run build test typecheckWhen to reach for this:
- Monorepo with 3+ packages and repeated
build/testin CI. - Shared libs must compile before apps typecheck.
- You want cache hits across developers and CI runners.
Working Example
platform/
turbo.json
package.json # workspaces
apps/
api/package.json
packages/
shared/package.json
// package.json (root)
{
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo run build",
"test": "turbo run test",
"dev": "turbo run dev --parallel"
},
"devDependencies": {
"turbo": "^2.3.0",
"typescript": "^5.6.0"
}
}// packages/shared/package.json
{
"name": "@acme/shared",
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "node --import tsx --test"
}
}// apps/api/package.json
{
"name": "@acme/api",
"dependencies": { "@acme/shared": "workspace:*", "fastify": "^5.0.0" },
"scripts": {
"build": "tsc -p tsconfig.json",
"dev": "tsx watch src/server.ts",
"test": "node --import tsx --test"
}
}npm install
npx turbo run build --filter=@acme/api...What this demonstrates:
^buildruns dependency package builds before dependents.--filter=@acme/api...includes the API and its workspace dependencies.outputstells Turbo what to cache between runs.
Deep Dive
How It Works
- Turbo hashes task inputs (source, env vars, dependencies) and restores
outputsfrom cache on cache hit. dependsOnbuilds a DAG;testwaits forbuildartifacts.- Remote cache (Vercel or self-hosted) shares hits across CI machines.
devtasks are typicallypersistent: trueand not cached.
Task Configuration
| Field | Purpose |
|---|---|
dependsOn | Upstream task ordering (^build = deps first) |
outputs | Folders restored from cache (dist/**) |
inputs | Fine-tune hash (default: package files) |
env | Env vars that bust cache when changed |
TypeScript Notes
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"typecheck": { "dependsOn": ["^build"] }
}
}- Separate
typecheckfrombuildif you want faster CI jobs that skip emit when only checking types. - Ensure each package
tsconfigemits todist/consistently for cache paths.
Gotchas
- Missing
outputs- Turbo caches nothing useful; every build reruns fully. Fix: declaredist/**per compile task. - Wrong filter syntax -
@acme/apialone skips dependency builds. Fix: use@acme/api...(three dots) for dependents chain. - Caching tests with side effects - Tests hitting real DB get false green from cache. Fix: exclude integration tests from cached tasks or use unique
inputs. - Env secrets in hash -
envlists bust cache when tokens change. Fix: only list env vars that affect build output. - dev task cached by mistake - Watch mode should not cache. Fix:
"persistent": trueand nooutputsondev.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
npm run -w scripts only | 2-package monorepo | CI time grows linearly with packages |
| Nx | Generators, affected graph, enforcement | Team wants minimal config |
| Bazel | Huge polyglot repos | Node-only backend shop |
FAQs
Does Turbo replace npm workspaces?
No. Turbo runs on top of workspaces. You still use npm ci and workspace:* linking.
How do I run one app in dev?
npx turbo run dev --filter=@acme/apiAdd --parallel when running multiple dev servers.
What should outputs include?
Compiled dist/**, generated OpenAPI specs, or *.tsbuildinfo if you cache incremental builds.
How does remote cache auth work?
turbo login and TURBO_TOKEN in CI. Self-host with S3-compatible storage for air-gapped teams.
Can Express and Fastify apps coexist?
Yes in separate apps/* workspaces. Shared code lives in packages/ without framework imports.
Should test depend on build?
Yes when tests import dist/ or compiled types. Pure tsx source tests may omit build dependency if configured carefully.
How do I bust cache after toolchain bump?
Changing root package-lock.json or turbo.json invalidates hashes globally for affected tasks.
Is turbo needed for single API?
No. Add when a second package or service appears and CI exceeds ~5 minutes routinely.
How do Docker builds use Turbo?
Run turbo prune --scope=@acme/api --docker to generate a minimal subset for multi-stage images.
Does NestJS monorepo use Turbo?
Nest has its own monorepo mode; many teams still add Turbo for cross-framework packages/ libs.
Related
- Workspaces & Monorepos - workspace linking
- Multi-Service Monorepo - deployable boundaries
- Nx for Node Backends - alternative orchestration
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.