$ Vraj Ved _

Latest Commit: September 17, 2026

← Back to blog

Setting up a MonoRepo from Scratch

2026-01-18·15 min Read

So what is a Monorepo ?

MonoRepo is short for "Monolithic Repository", it is a repository that contains multiple projects related with each other often with shared dependencies and are stored in one single place.

The architectural decision of organizing a monorepo has gained lots of traction in companies like Google, Meta and X (formerly twitter) due to ease of development and code sharing. Google's MonoRepo is famous for hosting the entire codebase of google itself and Google has developed its own version control system called Piper to manage the vast codebase

A mono repo can contain variety of projects including frontend, backend, shared libraries, and system level projects.

Why should you choose a Monorepo ?

There are several reasons mainly

  • Code Sharing and Reusability: By having everything in one place, it is significantly easier to share and reuse code across different projects without worrying about versioning issues and complex depenedency management. For Example, if you have a design system, you can reuse that across multiple frontend applications, or even better a shared library which has to be updated constantly. Updating a shared library across multiple repositories can be a nightmare.

  • Atomic Changes across different projects: Developers can make changes that span multiple projects in only one commit, for example, a change in the backend API can be reflected in the frontend in the same commit, ensuring consistency, synchronization and overall reducing the number of PRs to review, making dev processes faster and efficient.

  • Centralized Versioning: Since everything shares a single version control history, it is easier to track changes, rollbacks and manage releases. You don't need to worry about managing multiple versions systems across different repos.

  • Simplified Dependency Management: We can share dependencies and their versions across different projects completely eliminating descrepencies, compatibility issues and "works on my machine" problems. Dependency management is straightforward, since it can be coordinated across different projects with minor changes.

  • Simplified CI/CD Pipeline: Continuous Integration and Continuous Deployment pipelines are easier to setup. Developers can simply trigger a single pipeline that tests, builds and deploys multiple projects. Since everything is in one place.

  • Improved Collaboration: Teams working on different aspects of a project can collaborate more effectively. It is easier to track and make cross-functional changes to ensure that everything is aligned.

Setting up a MonoRepo from Scratch

The main challenge of setting up a monorepo is that it is complex. There are several tools to help manage monorepos. But to understand the system better, we will set up a simple monorepo from scratch using npm and TypeScript.

It took me around 6 hours to figure out and set up my first monorepo for a production ready project with many required packages and several architectural decisions, so I hope this blog saves you some time.

A monorepo has a simple structure

/monorepo
|- /client
|  |- /project1
|  |- /project2
|- /server
|  |- /backend1
|  |- /backend2
|- /shared
|  |- /library1
|  |- /library2
|- .gitignore
|- README.md
|- package.json

Don't get overwhelmed and worry about the proper structure, we'll set this up step by step.

Step by Step Guide to setup


Step 1: Initialize the Monorepo

First we create a new directory for our monorepo and initialize it with npm.

mkdir monorepo
cd monorepo
npm init -y

This will create a package.json file in the root of the monorepo.


Step 2: Setting up TypeScript

Next, we need to set up TypeScript in our monorepo. We will install TypeScript as a dev dependency and create a tsconfig.json file.

npm install typescript --save-dev
npx tsc --init

You can choose to rename tsconfig.json to tsconfig.base.json if you want to have separate tsconfig files for each project later.

A good practice is to setup the tsconfig.base.json in the root and have each project extend it with their own tsconfig.json files. So that we dont duplicate the same configuration across different projects.

{
  "compilerOptions": {
    "target": "ES2020",
    "moduleResolution": "Node",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  }
}

Step 3: Creating the Project Structure

Now, we will create the project structure for our monorepo. We will create directories for client, server and shared libraries.

mkdir client server shared

This will create the base structure for our monorepo. You can create subdirectories for each project as needed.

How it looks so far:

/monorepo
|- /client
|- /server
|- /shared
|- package-lock.json
|- package.json
|- tsconfig.json

Yes, we are mostly done setting up the monorepo structure. You can now start adding your projects in the respective directories.


Step 4: Adding and Configuring each Project

  • Client Project

    Each project can have its own package.json and tsconfig.json files. You can navigate to each project directory and initialize them with npm and TypeScript.

    cd client/project1
    npm init -y
    npm install typescript --save-dev
    npx tsc --init
    

    Repeat this for each project in the monorepo.

    Alternatively

    If you have one single project and are using a monorepo, you can simply initialize your frontend app in the client directory and backend app in the server directory without creating subdirectories.

    cd client
    npm create vite@latest . 
    
  • Server Project

    You can set up your backend project similarly in the server directory.

    cd ./server
    npm init -y
    npm install typescript --save-dev
    

    Make sure that the server tsconfig.json extends the root tsconfig.base.json

    {
      "extends": "../tsconfig.base.json",
      "compilerOptions": {
        "outDir": "./dist",
        "rootDir": "./src"
      },
      "include": ["src"]
    }
    

    If you want to set up a proper Monolithic REST API backend, you can refer to my other blog in which I have explained the structure in detail and best practices to follow - Production Ready REST APIs in a Monolithic Architecture


Step 5: Setting up and Using npm workspaces

At this point we have a basic monorepo with multiple projects set up. This doesn't help us much yet, since we can't share dependencies or code between different projects.

We need a way to

  • Manage dependencies across different projects
  • Avoid duplicate installations of the same package
  • Link shared libraries across different projects

npm workspaces help us achieve this

To set up npm workspaces, we need to add a workspaces field in the root package.json file.

{
  "name": "monorepo",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "client/*",
    "server/*",
    "shared/*"
  ],
  "devDependencies": {
    "typescript": "^5.x"
  }
}

The workspace configuration depends on your project structure.

If you are using nested projects (e.g. client/project1):

"workspaces": ["client/*", "server/*", "shared/*"]

If you are using single projects directly inside each folder:

"workspaces": ["client", "server", "shared"]

Choose the pattern that matches your structure to avoid confusion.

The "private": true is important so that we don't accidentally publish our monorepo as an npm package. (yeah, it happens).

Now npm knows that client, server and shared are workspaces and it will manage dependencies across them. Also enabling us to run commands through root by npm run dev:client and etc. which we will set up later.

Now we simply run npm install in the root of the monorepo. This will install all dependencies for all projects in the monorepo and npm will hoist most dependencies to a single node_modules directory in the root when possible.


Step 6: Setting up Shared properly

The shared directory is where you can put shared libraries, components or shared types that can be used across different projects. You can set up a shared library like this:

cd shared/library1
npm init -y

You can then edit the package.json to add build scripts and dependencies as needed.

{
  "name": "@workspace/library1",
  "version": "0.1.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc"
  }
}

This way, you can build the shared library and use it in other projects by adding it as a dependency. just by using an import statement

import { something } from '@workspace/library1'

We now have to configure TypeScript which extends tsconfig.base.json in the root to properly resolve the paths.

In shared/library1/tsconfig.json:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "declaration": true,
    "module": "CommonJS"
  },
  "include": ["src"]
}

Then we create a source directory to make sure everything compiles correctly.

mkdir src
touch src/index.ts

Step 7: Linking Shared into Client and Server

To use the shared library in your client and server projects, you need to add it as a dependency in their respective package.json files.

In client/package.json and server/package.json, add the following:

"dependencies": {
  "@workspace/library1": "*"
}

At this point, the shared library behaves like a normal npm package, except it is resolved locally by the workspace.

Step 8: Running and Building Projects

Now we add scripts in the root package.json to run and build each project.

{
  "scripts": {
    "build": "npm run build --workspaces"
  }
}

npm will run the build script in each workspace.

You can add individual scripts for each project as well. (recommended)

{
  "scripts": {
    "dev:client": "npm --workspace=client start",
    "dev:server": "npm --workspace=server start"
  }
}

Note: Always remember to build shared first since it may contain dependencies used by both client and server. npm workspaces automatically resolves the build order, but it’s important to understand that shared packages must be built before dependents.


Conclusion

You saw through this blog, about the basics of MonoRepos, Why should you choose one, and how to set up one from scratch using npm and TypeScript.

In case you followed along, you should now have a working monorepo with multiple projects and shared libraries. You also saw how really freaking complex this thing is.

Key takeaways from this blog

  • • A MonoRepo is a single repository that contains multiple related projects.
  • • MonoRepos promote code sharing, reusability and easier dependency management.
  • • Setting up a MonoRepo involves initializing a root package.json, setting up TypeScript, creating project structure and configuring npm workspaces.
  • • Shared libraries can be created and used across different projects in the monorepo.

Research and References