TechLead
Lesson 13 of 20
5 min read
DevTools & Productivity

Monorepo Tools

Set up and manage monorepos with Turborepo, Nx, npm workspaces, and pnpm for scalable multi-package projects

What is a Monorepo?

A monorepo is a single repository that contains multiple projects, packages, or applications. Instead of having separate Git repositories for your web app, API server, shared utilities, and component library, everything lives in one repository. This approach is used by Google, Meta, Microsoft, and many large open-source projects (React, Next.js, Babel, Jest).

Monorepo Benefits

  • Code Sharing: Share code between packages without publishing to npm. Import directly from workspace packages.
  • Atomic Changes: A single commit can update the API, shared types, and frontend simultaneously.
  • Consistent Tooling: Share ESLint, Prettier, TypeScript, and test configurations across all packages.
  • Simplified CI/CD: One pipeline that builds, tests, and deploys everything in the right order.
  • Dependency Management: Shared dependencies are deduped. Version conflicts are caught immediately.

Monorepo Structure

# Typical monorepo structure
my-monorepo/
  apps/
    web/              # Next.js frontend
      package.json
      src/
    api/              # Express/Fastify backend
      package.json
      src/
    admin/            # Admin dashboard
      package.json
      src/
  packages/
    ui/               # Shared React component library
      package.json
      src/
    shared/           # Shared utilities and types
      package.json
      src/
    config-eslint/    # Shared ESLint config
      package.json
    config-typescript/ # Shared tsconfig
      package.json
  package.json        # Root workspace config
  turbo.json          # Turborepo config
  pnpm-workspace.yaml # pnpm workspace config

Turborepo

Turborepo is a high-performance build system for JavaScript and TypeScript monorepos. It provides intelligent caching (local and remote), parallel task execution, and dependency-aware task ordering. It is framework-agnostic and works with npm, yarn, or pnpm.

# Create a new Turborepo project
npx create-turbo@latest my-monorepo

# Or add Turborepo to an existing monorepo
npm install turbo --save-dev
// turbo.json - Turborepo configuration
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"]
    },
    "lint": {
      "dependsOn": ["^build"]
    },
    "typecheck": {
      "dependsOn": ["^build"]
    },
    "test": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "test/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
# Turborepo commands
npx turbo build                    # Build all packages in dependency order
npx turbo build --filter=web       # Build only the web app and its dependencies
npx turbo build --filter=web...    # Build web and everything it depends on
npx turbo build --filter=./apps/*  # Build all apps
npx turbo dev                      # Start dev servers for all packages
npx turbo lint test --parallel     # Run lint and test in parallel
npx turbo build --dry-run          # Show what would run without executing
npx turbo build --graph            # Generate a task dependency graph

pnpm Workspaces

pnpm is the preferred package manager for monorepos. It uses a content-addressable store that deduplicates packages across projects, saving significant disk space and installation time.

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"
# pnpm workspace commands
pnpm install                           # Install all workspace dependencies
pnpm add zod --filter @myorg/shared    # Add dep to specific package
pnpm add -D vitest --filter web        # Add dev dep to web app
pnpm run build --filter web            # Run build in web package
pnpm run build --filter "...web"       # Build web and all its deps
pnpm run test -r                       # Run test in all packages recursively
pnpm --filter "./packages/**" build    # Build all packages

Nx

Nx is a more full-featured build system compared to Turborepo. It includes code generators, dependency graph visualization, affected command analysis, and plugins for specific frameworks (React, Next.js, Angular, Node.js).

# Create a new Nx workspace
npx create-nx-workspace@latest my-org

# Add Nx to an existing monorepo
npx nx@latest init

# Nx commands
npx nx build web                    # Build the web app
npx nx affected --target=build      # Build only affected projects
npx nx affected --target=test       # Test only affected projects
npx nx graph                        # Open interactive dependency graph
npx nx run-many -t build test lint  # Run multiple targets
npx nx generate @nx/react:component # Generate code with schematics

Sharing Code Between Packages

// packages/shared/package.json
{
  "name": "@myorg/shared",
  "version": "0.0.0",
  "private": true,
  "exports": {
    ".": "./src/index.ts",
    "./types": "./src/types.ts",
    "./utils": "./src/utils.ts"
  }
}

// packages/ui/package.json
{
  "name": "@myorg/ui",
  "version": "0.0.0",
  "private": true,
  "exports": {
    ".": "./src/index.tsx",
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx"
  },
  "dependencies": {
    "@myorg/shared": "workspace:*"
  }
}

// apps/web/package.json
{
  "name": "web",
  "dependencies": {
    "@myorg/shared": "workspace:*",
    "@myorg/ui": "workspace:*"
  }
}
// apps/web/src/app/page.tsx - Using shared packages
import { Button } from '@myorg/ui/button';
import { formatDate } from '@myorg/shared/utils';
import type { User } from '@myorg/shared/types';

Monorepo Best Practices

  • Keep packages small and focused: Each package should have a clear, single responsibility.
  • Use internal packages for shared code: Do not duplicate code between apps. Extract shared logic into packages.
  • Share configurations: Create config packages for ESLint, TypeScript, and Prettier settings.
  • Use remote caching in CI: Turborepo and Nx both support remote caching to avoid rebuilding unchanged packages.
  • Choose pnpm for monorepos: Its strict dependency resolution and workspace protocol prevent phantom dependencies.

Continue Learning