Monorepos

31. October, 2024 7 min read Development

And Their Convenience

As projects grow in complexity, managing code across multiple repositories becomes challenging. Monorepos have emerged as a popular solution to handle complex codebases, enabling development teams to manage related projects within a single repository.

In this post, we’ll explore the concept of monorepos, their benefits, drawbacks, and the tools available to manage them effectively. We’ll also dive into creating a monorepo using Turborepo and explore alternative tools that cater to different needs and team structures.

What is a Monorepo?

A monorepo (short for “monolithic repository”) is a software development strategy where multiple projects, often related but distinct, are stored within a single repository. Rather than splitting each project into its own repository, monorepos consolidate them, facilitating centralised management and a shared codebase.

This structure is especially popular in large companies like Google, Facebook, and Microsoft, where multiple teams might work on interdependent projects that benefit from a shared environment. In smaller teams, monorepos can simplify dependency management, testing, and deployment.

Consider a hypothetical project with two applications and one shared library. A monorepo could look like this:

my-monorepo/
├── apps/
│   ├── web/      # the main application
│   └── docs/     # write your documentation
│   └── apis/     # serves apis
├── packages/
│   └── ui/       # shared UI components
└── config.json   # global config file

The “apps” folder contains individual applications and the “packages” folder contains reusable code in this setup.

Benefits and Drawbacks

Monorepos offer several conveniences, especially when managing complex or interdependent projects. Here are some key advantages:

  • Unified Codebase: Monorepos allow developers to manage all related code in one place. For instance, updates to shared libraries are immediately available to all projects without needing to update external dependencies.
  • Simplified Dependency Management: Shared packages, like custom libraries or configurations, can be easily reused across projects without needing to be published in a package registry. This avoids the pain of version mismatches and ensures compatibility across applications.
  • Better Collaboration: With a monorepo, team members can contribute to multiple projects more seamlessly. For example, if someone improves a shared library, the improvements automatically benefit every project that depends on it.
  • Efficient Builds with Caching: Tools like Turborepo offer caching and incremental builds, speeding up the development process. In a monorepo, you can avoid re-running expensive build steps if a package or app hasn’t changed, significantly reducing build times.

Despite their benefits, monorepos aren’t always the best choice. Here are a few considerations:

  • Complexity in Large Teams: For large teams with diverse projects, a monorepo can introduce complexity, especially with managing pull requests and versioning. This can be mitigated with proper tooling and conventions but requires extra effort to maintain.
  • Slow Clone and Build Times Without Caching: Without efficient caching, monorepos can lead to slow build times. Cloning or pulling changes may also become slower as the repository grows, although tools like Git sparse-checkout can help manage this.
  • Dependency Conflicts: With multiple projects in one repo, dependency conflicts can arise. Resolving these conflicts requires more diligence, as changes to shared packages can inadvertently affect other projects.

Monorepos vs. Single Repos

When organizing a codebase, teams often face a key decision: whether to structure projects in a monorepo or maintain single repos for each project. Both approaches have their merits, but choosing the right one depends on your team’s needs, project scale, and collaboration style. Let’s dive into the pros and cons of each setup to help you determine the best fit for your development workflow.

Aspect Monorepo Single Repo
Code Reuse Simplifies code sharing with shared packages. Requires external dependencies or duplication for shared code.
Dependency Management Centralised, with direct access to shared dependencies. Separate dependencies, can cause version mismatches across projects.
Build Efficiency Faster with caching tools; may get slower with large codebases. Faster for individual repos but lacks cross-project build efficiency.
Scalability Complex to scale for large codebases without proper tooling. Easier to scale individual projects separately.

### Setting Up a Monorepo with Turborepo

Turborepo is a high-performance monorepo tool designed to optimize build times and provide powerful features like remote caching and task pipelines. Here’s a guide to setting up a monorepo with Turborepo.

  1. Initialize a New Monorepo by running npx create-turbo@latest.
  2. Start the example, cd into the folder and run npm run dev.
  3. Configure Turborepo

In the root of your monorepo, you’ll find a turbo.json configuration file. This file defines the tasks for each part of your codebase. Here’s an example configuration:

{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "lint": {},
    "test": {}
  }
}

The “pipeline” defines tasks, such as “build”, “lint”, and “test”, which can be run across the monorepo. The “dependsOn” property allows Turborepo to intelligently build dependencies in the correct order.

  1. Add applications and shared packages

For example, create a ui library and two applications through mkdir -p packages/ui apps/web apps/docs. In each of these folders, you can initialize a separate project with its own package.json.

  1. Using Shared Code

In “apps/web” or “apps/docs”, you can import the shared ui package directly, making code sharing seamless. For example, create a Button package:

// packages/ui/Button.js
import React from 'react';

export const Button = ({ children }) => <button>{children}</button>;

then, use it in “apps/web”:

import { Button } from '@my-monorepo/ui';

const HomePage = () => <Button>Click me!</Button>;

Alternative Tools

Turborepo isn’t the only option for managing monorepos. Here’s a look at some other popular tools:

  • Nx: Nx is a powerful monorepo tool that provides dependency graph visualization, caching, and a CLI. It’s popular for larger monorepos and offers out-of-the-box support for frameworks like React, Angular, and NestJS.
  • Lerna: Lerna has been a go-to tool for monorepos, focusing on publishing and managing versions. While not as optimized for caching as Turborepo, it’s highly compatible with tools like Yarn workspaces.
  • Bazel: Bazel, developed by Google, is designed for large-scale codebases. It offers cross-language support and advanced caching but has a steep learning curve.
  • Rush: Rush, developed by Microsoft, is designed for large JavaScript and TypeScript monorepos. It includes powerful features for dependency management and versioning.

Best Practices

To get the most out of a monorepo, certain practices can help streamline development, improve collaboration, and maintain code quality. By implementing these best practices, you can avoid common pitfalls and fully leverage the power of a monorepo, even as your project scales and team grows. Here are some essential guidelines to consider.

  • Use Version Control Efficiently: With many projects in one repo, managing branches and PRs becomes crucial. Consider using tools like GitHub Actions or GitLab CI/CD for continuous integration.
  • Establish Clear Ownership and Access Control: Assign responsibility for packages or apps to specific team members to avoid confusion.
  • Automate Dependency Updates: Use tools like Renovate or Dependabot to automate dependency updates and avoid version drift.
  • Enable Caching and Incremental Builds: Tools like Turborepo, Nx, and Bazel provide incremental builds and caching, which are essential for efficiency in large monorepos.

Summary

In summary, monorepos offer a unified, efficient way to manage multiple interdependent projects, making them ideal for teams that benefit from shared code and centralised workflows. However, they require careful setup and the right tools, like Turborepo, Nx, or Lerna, to maintain performance and organisation as they grow. By understanding both the advantages and challenges of monorepos, along with implementing best practices, you can decide whether this approach suits your team’s development needs and helps streamline your project’s lifecycle.

‘Till next time!