Monorepo is a software development strategy that creates shared libraries and dependencies in a single repository. By centralizing codebases, improving collaboration, and streamlining dependency management, Monorepos offer a solution for crucial dependency management.
This article explains the process of creating monorepos using well-known tools and technologies like TypeScript, Turborepo, and Nx, allowing you to choose the best tool based on your requirements.
If you’re a hands-on learner, I created a video to walk through this process. Feel free to follow along as you read!
Imagine managing three separate applications for a project: a Web App, a Mobile App, and an Admin Panel. Each resides in its git repository and shares a common UI component library. Now think, when you need to update a critical button component for accessibility.
The process becomes a headache:
Sounds frustrating, right? This is where monorepos comes in—a simpler approach where all your projects live under one roof, making it easier to coordinate changes and maintain consistency across your codebase.
Let’s start with monorepos…
In the following sections, we will create basic utils monorepo which can be used anywhere in the project.
In this section, we will build a monorepo manually using just TypeScript and npm.
npm init -y
npm install typescript
npm install --save-dev @types/node ts-node
Create a packages
folder and a utils
directory within it to act as a shared library.
mkdir -p packages/utils
Init npm inside utils
with scope @monorepo,
npm init --scope @monorepo --workspace ./packages/utils -y
packages/utils/package.json
will look like this,
{
"name": "@monorepo/utils",
"version": "1.0.0",
"main": "index.js",
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
This will also add a workspace field in the root’s package.json like below. If you have multiple monorepos, you can update workspace
using wildcards like "packages/*".
"workspaces": [
"packages/utils" or "packages/*"
]
root
and packages
, npx tsc --init && cd packages/utils && npx tsc --init
packages/utils’s tsconfig.json
as needed. In my case it is,"rootDir": "./src",
"outDir": "./dist", // make sure to update main
Note: Make sure to update the main field in the package.json based on your outDir. For me, it is
dist/index.js
.
utils/tsconfig.json
.root’s tscofig.json
"compilerOptions": {...},
"references": [
{"path": "./packages/utils" },
]
"compilerOptions": {
...,
"baseUrl": "./",
"paths": {
"@monorepo/utils": ["packages/utils/*"],
}
},
packages/utils/src/index.ts
// packages/utils/src/index.ts
export function multiply(a: number, b: number): number {
return a * b;
}
"build": "tsc --build"
in the root and utils package.json
scripts, then build with npm run build
.@monorepo/utils
into the root file to verify the function works as expected.npm install @monorepo/utils
import { multiply } from "@monorepo/utils";
const result = multiply(2, 3);
console.log(`result: ${result}`);
Yeah, You have created a basic monorepo setup. You can build as many as monorepos and use them like this.
Monorepo’s with only typescript is useful for small, simple projects or experimental purposes without requiring third-party tooling.
Next, let’s create monorepo with Turborepo.
Note: Repeat till step-3 to init setup in both turbo and nx, then follow below steps.
Turborepo is a high-performance monorepo build tool that simplifies dependency management and speeds up builds through caching.
Follow the below steps to configure monorepo using Turborepo.
npm install turbo -D
packageManager
to each package.json
to ensure compatibility with Turborepo."packageManager": "npm@<npm-version>",
Create a turbo.json
in the root directory:
{
"$schema": "https://turborepo.org/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false
}
}
}
tasks
section defines tasks like build
dependencies and caching behavior.data-transform
package to illustrate Turborepo's dependency management.data-transform
,export function transformData(data: any): any {
return data.map((item) => ({ ...item, transformed: true }));
}
npx turbo build
You can install @monorepo/data-transform
into the root or utils package, and verify it's working as expected.
Ideal for mid-size projects that need optimized build times and clear dependency management.
Now let’s create monorepo with NX.
Nx offers a highly integrated monorepo experience, by implementing an efficient caching mechanism, storing results from previous executions.
It includes code generation tools that scaffold various applications, libraries, and components like React, Angular, Vue, etc…
Nx provides Nx cloud for distributed caching, making builds even faster in CI/CD and visual dependency graphs for a better understanding of project dependencies for complex projects.
Follow the below steps to configure monorepo using Nx.
npx nx@latest init
nx.json
file and scaffold dependencies for managing projects. Add the below script in nx.json,{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"defaultBase": "master",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/dist"], /** output directory **/
"cache": true
}
}
}
build
tasks. You can run a single task like this,npx nx build @monorepo/utils
npx nx run-many -t build
Nx is suited for large projects with complex dependency chains, making it ideal for enterprise-level applications.
Monorepo is a powerful way to unify your codebase, improve consistency, and reduce overhead, especially in teams handling multiple interconnected projects. Adopting a monorepo approach can lead to a cleaner development workflow, easier dependency management, and more scalable infrastructure for your applications.
Choosing the right tool from all three depends on factors like your project size, the frequency of cross-project dependencies, and the level of automation required: