Monorepo Patterns
Quick Summary
A monorepo (mono-repository) stores multiple projects, packages, or services in a single Git repository. Companies like Google, Facebook, and Microsoft use monorepos for thousands of projects. This approach simplifies dependency management, code sharing, and atomic changes — but requires specialized tooling at scale.
Monorepo vs Multi-Repo
flowchart TD
subgraph "Multi-Repo"
R1["frontend<br/>(github.com/co/frontend)"]
R2["backend<br/>(github.com/co/backend)"]
R3["shared-lib<br/>(github.com/co/shared-lib)"]
R1 -.->|npm dependency| R3
R2 -.->|npm dependency| R3
end
subgraph "Monorepo"
M["github.com/co/platform"]
M --> P1["packages/frontend/"]
M --> P2["packages/backend/"]
M --> P3["packages/shared-lib/"]
end
| Aspect | Monorepo | Multi-Repo |
|---|---|---|
| Code sharing | Import directly, always latest | Publish + version + install |
| Atomic changes | One PR changes frontend + backend + lib | Three PRs across three repos |
| Dependency management | Single lockfile / consistent versions | Per-repo, can drift |
| CI/CD | Complex (what to build?) | Simple (build everything) |
| Access control | Everyone sees everything (by default) | Per-repo permissions |
| Repository size | Can grow very large | Each repo stays small |
| Onboarding | Clone once, see everything | Clone many repos |
Monorepo Project Structure
my-platform/
├── .github/
│ └── workflows/ # CI/CD for all packages
├── packages/
│ ├── frontend/
│ │ ├── package.json
│ │ └── src/
│ ├── backend/
│ │ ├── package.json
│ │ └── src/
│ ├── shared-utils/
│ │ ├── package.json
│ │ └── src/
│ └── docs/
│ ├── package.json
│ └── src/
├── package.json # Root workspace config
├── turbo.json # Turborepo config (or nx.json)
└── pnpm-workspace.yaml # Workspace definition
Monorepo Tooling
| Tool | Ecosystem | Key Feature |
|---|---|---|
| Turborepo | JavaScript/TypeScript | Remote caching, task pipelining |
| Nx | Multi-language | Dependency graph, affected detection |
| Lerna | JavaScript (legacy) | Package publishing |
| Bazel | Multi-language (Google) | Hermetic builds, massive scale |
| Pants | Python, Go, JVM | Incremental, dependency-aware |
Git Techniques for Monorepos
Sparse Checkout (Clone Only What You Need)
# Clone without files
git clone --no-checkout git@github.com:company/platform.git
cd platform
# Enable sparse checkout
git sparse-checkout init --cone
# Check out only the packages you need
git sparse-checkout set packages/frontend packages/shared-utils
# Now only those directories are on disk
ls packages/
## frontend/ shared-utils/
Shallow Clone + Sparse
# Fastest possible clone for CI
git clone --depth 1 --filter=blob:none --sparse git@github.com:company/platform.git
cd platform
git sparse-checkout set packages/backend
Path-Based CI (Only Build What Changed)
# Check which packages changed (for CI)
git diff --name-only HEAD~1 | grep "^packages/" | cut -d'/' -f2 | sort -u
When to Use a Monorepo
| Scenario | Monorepo? |
|---|---|
| Tightly coupled services sharing code | ✅ Yes |
| Frontend + backend sharing types/schemas | ✅ Yes |
| Many small packages under unified CI | ✅ Yes |
| Completely independent projects | ❌ Separate repos |
| Open-source project with many contributors | ❌ Usually separate |
| Very large binary assets | ❌ Git handles this poorly |
Best Practices
- Use workspace tooling (Turborepo, Nx) — don't manage monorepos manually
- Enable sparse checkout for large repos — developers shouldn't need all projects
- Path-scoped CI — only test/build what actually changed
- Shared linting/formatting — enforce consistency at the root level
- CODEOWNERS file — define who reviews which directories
What's Next
- GitHub Actions & CI/CD — Automate builds and deployments
- Submodules & Subtrees — Alternative approaches to multi-project Git