Lesson 33 of 49 intermediate

What is a Monorepo?

One Repo to Rule Them All

Open interactive version (quiz + challenge)

Real-world analogy

Imagine you have 3 pets: a cat, a dog, and a parrot. You COULD keep each in a separate house (3 repos). OR you could keep them all in ONE big house with separate rooms (monorepo). They share the same kitchen (shared code), same electricity (build tools), but each has their own space!

What is it?

A monorepo is a single Git repository that contains multiple projects (frontend, backend, shared libraries). Tools like Turborepo and pnpm workspaces make it efficient by sharing dependencies and running tasks in parallel.

Real-world relevance

Companies like Google, Meta, and Microsoft use monorepos. Your NestJS backend and React frontend can share TypeScript types — change a type once, both sides update instantly!

Key points

Code example

// Typical monorepo structure:
my-project/
├── apps/
│   ├── frontend/     ← React app (Vite + TypeScript)
│   └── backend/      ← NestJS app
├── packages/
│   └── shared/       ← Shared types, utils
├── package.json      ← Root config
├── pnpm-workspace.yaml
└── turbo.json        ← Turborepo config

// pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"

// In frontend, you can import shared types:
import { UserDTO } from '@myproject/shared';

// turbo.json — configure what to cache
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"]
    }
  }
}

Line-by-line walkthrough

  1. 1. Typical monorepo structure:
  2. 2.
  3. 3.
  4. 4.
  5. 5.
  6. 6.
  7. 7.
  8. 8.
  9. 9.
  10. 10.
  11. 11.
  12. 12. pnpm-workspace.yaml
  13. 13.
  14. 14.
  15. 15.
  16. 16.
  17. 17. In frontend, you can import shared types:
  18. 18. Importing required dependencies
  19. 19.
  20. 20. turbo.json — configure what to cache
  21. 21. Opening block
  22. 22.
  23. 23.
  24. 24.
  25. 25.
  26. 26. Closing block
  27. 27.
  28. 28.
  29. 29. Closing block
  30. 30. Closing block
  31. 31. Closing block

Spot the bug

// packages/shared/types.ts
export interface User { name: string; }

// apps/frontend/app.tsx
import { User } from '../../packages/shared/types';
Need a hint?
Is using deep relative paths the right approach in a monorepo?
Show answer
Deep relative paths (../../packages) are fragile and break when files move. Fix: use workspace package names: import { User } from '@myproject/shared' with proper package.json config.

Explain like I'm 5

You have a toy car, airplane, and shared batteries. You could keep each in separate boxes (separate repos), but then you need batteries in EVERY box! A monorepo is one big toybox with sections - all toys share the same batteries. New batteries? All toys benefit!

Fun fact

Google's monorepo has over 2 BILLION lines of code and 86 terabytes of data. One repo. Seriously. 🤯

Hands-on challenge

Create a folder structure with apps/frontend, apps/backend, and packages/shared. Add a shared types.ts file and try importing it from both apps!

More resources

Open interactive version (quiz + challenge) ← Back to course: Full-Stack Playbook