Monorepo with TypeScript: Code Organization and Best Practices

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Monorepo with TypeScript: efficient code organization for scalable projects



Modern application development often requires splitting code into several independent modules or packages. This raises a dilemma: store each package in a separate repository (polyrepo) or combine everything in one (monorepo). For TypeScript projects, a monorepo becomes an especially attractive solution, providing type consistency, simplified refactoring, and centralized dependency management. In this article, we will break down how to organize a monorepo with TypeScript, configure it, and use it effectively in development.



What is a monorepo and why is it needed for TypeScript?



A monorepo is an approach where the code of several projects (packages, libraries, microservices) is stored in a single repository. In the context of TypeScript, this is particularly useful because:



  • Shared types and interfaces: You can define common types in one package and reuse them across all others, avoiding duplication and desynchronization.
  • Unified configuration: tsconfig.json, linters, and formatters are configured once for the entire project.
  • Simplified refactoring: Changes to shared code or interfaces are immediately reflected in all dependent packages, and TypeScript will flag inconsistencies.
  • Atomic commits: Changes affecting multiple packages can be committed and pushed in a single operation.


However, a monorepo requires proper organization, otherwise it turns into chaos. The key tools for working with monorepos in TypeScript are npm/yarn/pnpm workspaces and TypeScript Project References.



Structure of a monorepo with TypeScript



Let's consider a typical monorepo structure for a web application consisting of a frontend, backend, and a shared utility library:



my-monorepo/├── packages/│   ├── shared/          # Shared types, utilities, constants│   │   ├── src/│   │   │   └── index.ts│   │   ├── tsconfig.json│   │   └── package.json│   ├── backend/         # Server application (e.g., Express)│   │   ├── src/│   │   │   └── app.ts│   │   ├── tsconfig.json│   │   └── package.json│   └── frontend/        # Client application (e.g., React)│       ├── src/│       │   └── App.tsx│       ├── tsconfig.json│       └── package.json├── tsconfig.base.json   # Base TypeScript configuration file├── package.json         # Root package.json (workspaces)└── .gitignore


In this structure:

  • packages/* — directory containing all project packages.
  • tsconfig.base.json — shared TypeScript configuration inherited by all packages.
  • package.json at the root declares workspaces.


Setting up a monorepo: step-by-step guide



1. Initialize the root project and configure Workspaces



Create the root directory and package.json file:

{  "name": "my-monorepo",  "private": true,  "workspaces": [    "packages/*"  ],  "scripts": {    "build": "npm run build --workspaces",    "lint": "eslint packages/*/src --ext .ts,.tsx",    "test": "jest"  }}

The "private": true parameter prevents accidental publication of the root package. "workspaces": ["packages/*"] tells npm where to look for child packages.



2. Create a base TypeScript configuration



File tsconfig.base.json at the project root:

{  "compilerOptions": {    "target": "ES2020",    "module": "ESNext",    "moduleResolution": "node",    "strict": true,    "esModuleInterop": true,    "ski                

Blogs

Book Recommendations