Understanding Monorepos: A Practical Guide
Introduction
Monorepos have become increasingly popular, especially for large-scale projects. In this blog, we will explore what monorepos are, when to use them, their benefits, and the challenges they bring. This guide will help you understand monorepos and get hands-on with setting up a simple one using TurboRepo
, along with examples of a packages
folder. By the end, you'll have a clear understanding of how monorepos can streamline your development process.
What is a Monorepo?
In simple terms, a monorepo is a way to manage multiple projects in a single Git repository. These projects can share code and dependencies, making it easier to maintain consistency across them. Unlike polyrepos (one project per repo), a monorepo allows for easier code sharing and management of dependencies between multiple projects.
Why Use a Monorepo?
Monorepos are most useful when:
You have multiple projects that share code (e.g., a UI library).
You need consistent tooling across projects.
You want to streamline development and deployment processes.
When Should You Avoid a Monorepo?
Monorepos aren’t necessary for every project. If you are working on a small project or a single application, you may not need one. Monorepos also require additional tooling to manage builds efficiently, which may not be necessary for small teams or projects.
Setting Up a Monorepo with TurboRepo
TurboRepo
is a tool that optimizes the development process by enabling you to build, lint, and test only what has changed. This makes monorepos much faster to work with. Let's create a simple monorepo setup using TurboRepo
.
Step 1: Install TurboRepo
Start by setting up a new project with TurboRepo. Install the package globally or as a dev dependency:
npm install turbo -g
Step 2: Project Structure
We'll create a structure where we have two applications (web
and admin
) and one shared package (ui-library
).
/my-monorepo
/apps
/web
/admin
/packages
/ui-library
Step 3: Setup turbo.json
The turbo.json
file is the configuration file for TurboRepo. It helps specify what tasks to run for different parts of your monorepo.
{
"pipeline": {
"build": {
"dependsOn": ["^build"]
},
"lint": {},
"test": {}
}
}
This turbo.json
defines a build
, lint
, and test
pipeline. The dependsOn
field ensures that each build depends on the builds of its dependencies.
Step 4: Setup packages Folder
Inside the packages
folder, we'll place shared code that can be reused by multiple apps. For example, we can create a UI component library.
Example: ui-library Package
- Create a simple UI component in
packages/ui-library/src/Button.tsx
.
import React from 'react';
const Button = () => {
return <button>Click me!</button>;
};
export default Button;
- Add
package.json
for theui-library
package:
{
"name": "ui-library",
"version": "1.0.0",
"main": "src/index.ts",
"dependencies": {
"react": "^17.0.0"
}
}
- Create a
tsconfig.json
for TypeScript support:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"jsx": "react",
"strict": true
}
}
Step 5: Setup apps Folder
We'll now configure our apps
to use this shared package from packages/ui-library
.
Example: web App
Create a simple React app in
apps/web
.Inside
apps/web/src/App.tsx
, import the sharedButton
component from theui-library
package:
import React from 'react';
import Button from 'ui-library/src/Button';
function App() {
return (
<div>
<h1>Welcome to the Web App</h1>
<Button />
</div>
);
}
export default App;
- Update the
package.json
of theweb
app to include the shared package:
{
"name": "web",
"version": "1.0.0",
"dependencies": {
"ui-library": "workspace:*"
}
}
Example: admin App
Create a similar React app in
apps/admin
.Use the same
Button
component from theui-library
package in theadmin
app.
Step 6: Running TurboRepo
Now, let’s build and run the entire monorepo using TurboRepo.
turbo run build
TurboRepo will automatically determine the build order based on the dependencies between your apps and packages. For example, if you make a change in ui-library
, TurboRepo will only rebuild the affected apps (web
and admin
).
Advantages of Using a Monorepo
Code Sharing: You can share code (e.g., UI components) between multiple projects without duplicating it.
Consistent Tooling: By sharing tools like ESLint, Prettier, and TypeScript configurations across projects, you maintain a consistent code style and structure.
Simplified Dependency Management: Dependencies are installed only once at the root, saving time and space.
Challenges and Solutions
Maintaining Configurations
As your monorepo grows, managing different configurations (e.g., ESLint, TypeScript, Tailwind) across multiple projects can become complex. To solve this, create configuration packages in the packages
folder.
Example:
{
"name": "eslint-config-custom",
"version": "1.0.0",
"main": "index.js"
}
Use these configurations across all apps by referencing them.
Slow CI Pipelines
In large monorepos, building every app/package on every commit can slow down your CI pipeline. TurboRepo solves this by caching builds and only rebuilding the parts of your monorepo that have changed.
Conclusion
Monorepos, when managed effectively, can make your development process much smoother, especially for larger teams and projects. Using tools like TurboRepo helps optimize workflows by caching and streamlining builds. While setting up a monorepo comes with its challenges, the benefits of shared code, consistent tooling, and simplified dependency management often outweigh the difficulties. Feel free to explore this monorepo setup with TurboRepo for your projects, and see how it can improve your development workflow! For further reading, consider exploring additional resources and documentation on monorepos and TurboRepo.
Subscribe to my newsletter
Read articles from Kishan Kareliya directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by