Skip to main content

Step 1

Monorepo vs polyrepo

7 views

Table of contents

As the number of projects grows, define boundaries and verification ownership before choosing folders. The repository model should fit team size, release independence, and permission boundaries.

1. Monorepo — many apps and services in one repository

workspace/
├── apps/
│   ├── web/
│   └── console/
├── services/
│   ├── api/
│   └── worker/
├── packages/
│   ├── contracts/
│   └── ui/
├── infra/
└── docs/

2. Benefits

  • Atomic changes — verify a screen and its API contract together
  • Shared code — keep types, contracts, and accessibility utilities in one place
  • Consistent tooling — align package, container, and verification commands
  • Refactor visibility — search the entire repository for impact
  • Change traceability — inspect docs, schemas, and deployment settings together

3. Costs

  • A larger checkout — more clone and indexing cost
  • Complex CI — find changed apps and their dependency graph
  • Harder permissions — protect sensitive areas in one repository
  • Tooling constraints — shared runtime and package policies are required

4. Polyrepo — one repository per release unit

github.com/example/web
github.com/example/api
github.com/example/console
github.com/example/worker

5. Benefits

  • Small repositories — faster clone and focused changes
  • Permission separation — access control per repository
  • Independent releases — a clear blast radius
  • Tool autonomy — each team can choose its runtime and cadence

6. Costs

  • Cross-repository changes — coordinate several PRs and versions
  • Duplicated code — the same utility gets implemented repeatedly
  • Version drift — TypeScript, Node, and contract versions diverge
  • Lower discoverability — harder to find another team's implementation

7. Decision guide

Situation Prefer
Individual or small team Monorepo
UI and API change together often Monorepo
Independent releases and strict permissions Polyrepo
Completely different languages and tooling Polyrepo
Stable shared libraries Polyrepo or an explicit packages boundary

8. Practical monorepo tooling

Choose one of pnpm workspaces, Nx, Turborepo, or Lerna according to the team's caching and graph needs.

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "services/*"
  - "packages/*"
pnpm --filter web add lodash
pnpm --filter web test
pnpm --filter "./services/*" typecheck

9. Change detection and CI

Building everything on every monorepo change makes feedback slow. Calculate changed paths and their dependencies; when a shared contract changes, widen consumer verification.

# ci.yml
filters:
  web: apps/web/**
  api: services/api/**
  contracts: packages/contracts/**

steps:
  - if: web or contracts
    run: pnpm --filter web test
  - if: api or contracts
    run: pnpm --filter api test

10. Control coupling

Being in one repository does not mean importing each other's internals. Otherwise the result is a big ball of mud.

  • Apps consume a service's public API and contract package only
  • Shared code lives in packages/ with an explicit owner
  • Database models and migrations stay inside the service boundary
  • Lint rules and CI fail on boundary violations

11. A practical checklist

  1. Split release units first; use role-based folder names.
  2. Document consumers of APIs, events, and database schemas.
  3. Give shared packages independent tests and version rules.
  4. Record the detected changes and actual verification list in CI logs.
  5. Keep contract, observability, and recovery standards consistent even across repositories.

Common traps

  • Deploying only one side and breaking the contract
  • Creating a workspace dependency loop
  • Splitting into a hundred packages too early
  • Defining permissions for sensitive data too late

Next

  • 02-ssot-where