The monorepo pattern uses a single version control repository for all of your projects and assets. You’ll lump your server-side, frontend, and infrastructure config files into one repository that everybody contributes to. Should you use it?
The pattern is popular with large tech companies. Google, Microsoft, and Facebook are among the organizations that use monorepos. What makes a monorepo so appealing?
Monorepo stands against multi-repo. The multi-repo pattern sees you create a new repository for each of your projects. It’s usually pretty clear-cut when a project deserves its own repository.
If you’re building an app, you might have three repositories:
- Server-side code:Your API (possibly with additional repositories for database schemas and documentation).
- Android project:Your app’s Android build, using Java or Kotlin.
- iOS project:Objective-C or Swift for your iOS app.
Here, everything that makes up your business is split into distinct units of functionality. With a monorepo, you abandon that grouping and always take the aggregate view. All of your assets belong together and are versioned accordingly.
One of the most oft-cited monorepo benefits concerns collaboration. In a monorepo, everyone sees everything. This aids clarity, facilitates openness, and makes it simpler for people on different teams to access each others’ work.
People can more easily work together on a task, even if it falls outside of their usual responsibilities. In a multi-repo scenario, you might need to ask for access to the relevant repository first. This adds friction that the monorepo approach avoids entirely.
Monorepos encourage everyone to hold ownership of the end goal rather than the individual pieces that comprise it. This can lead to people feeling more involved and better informed about what’s going on. An app developer might never touch the server components, but they can “feel” them evolving alongside their own work.
Ease of Abstraction
Monorepos also simplify code abstraction. It’s common to end up with similar functionality in your backend and frontend components. It makes sense to abstract this out into a shared library.
In the multi-repo paradigm, you’d need to create a new repository and then reference it in the others. That might be by building a package, or by using Git submodules. Either way, a lot of work is needed before your abstracted code can be fed back into the projects it was sourced from.
The process is more straightforward if you have a monorepo. You can move the code to a directory that makes sense, and then import it wherever it’s needed. An “abstraction” takes a matter of seconds. There’s similar convenience when it’s time to document the code: You can add the docs into your shared documentation system.
Multi-repos also exhibit practical hindrances when abstracting code. A development team member often lacks the necessary GitLab, GitHub, or Bitbucket permissions to create a new repository. This results in even greater overheads when a team leader must approve the new library and set up a repository. Monorepos help individual developers create reusable code by eliminating special abstraction processes.
Beyond creating the abstraction, monorepos simplify maintenance of shared modules. You don’t need to update each consumer of a package every time that you update it. All dependencies exist in the same codebase, so you can reference them without a package manager or dedicated versioning.
Using a monorepo can accelerate development velocity. We’ve touched on this in the previous sections, but it’s worth paying more attention to.
Monorepos reduce duplicate actions. If you need to refactor, it’s a single Find and Replace to apply the change across the codebase. There’s less switching between projects and fewer pull requests to review.
Contributors are given more ability to self-serve. As information isn’t siloed into team repositories, people are better equipped to go looking for the details that they need. This can reduce back-and-forth during code planning and review.
These characteristics also help when you’re refactoring an existing system. Trying to break a legacy application into its “frontend” and “backend” might be the wrong approach. Changes in one side will inevitably impact the other, so you’ll be continually reconciling the two repositories. Using a monorepo helps you to quickly refactor large swathes of the codebase, knowing that you’re impacting the whole system rather than piecemeal components.
Who Are Monorepos for?
Monorepos are a good fit for large teams with multiple projects. The benefits aren’t necessarily apparent with a handful of small projects. Monorepos work best at a scale where there would be perceptible inefficiency with a multi-repo approach.
Monorepos aren’t the same as monoliths. A monolith usually describes an application where the data and presentational layers are intermixed. The whole system is deployed every time a change is made.
Monorepos generally encapsulate multiple systems. They have multiple output artifacts, such as an API, website, and mobile app. Not all of the artifacts need to be produced for every change. Monorepos are meant to ease code sharing and refactoring. They’re not intended to result in a tightly coupled system that’s artificially bound together.
The pattern isn’t for every team. In many cases, multiple repositories will be easier to work with. They often feel more logical and can be easier to get to grips with. You won’t need to resolve merge conflicts in disparate parts of the system and it’s easier to handle releases. CI pipelines will be quicker, as you won’t be testing every project in each pipeline.
Dedicated repositories present a cleaner commit history, too. Monorepo histories are polluted by commits made to every project within the repo. This makes it harder to track how individual components have evolved.
Multiple repositories are easier to integrate with version control software like GitHub and GitLab. These tools assume a one-to-one mapping between repositories and projects. It can be cumbersome to track issues and pull requests in a monorepo. You’ll need to diligently use tags to scope each issue to the appropriate project.
Finally, take note that most organizations with monorepos are using specialized infrastructure to support them. Git isn’t designed for monorepos, and it can struggle if you reach sufficient scale. Having millions of objects in your commit history can result in slowdowns when Git needs to walk the graph.
The monorepo pattern simplifies code sharing and improves visibility into your assets. This comes at the cost of a clean commit history, increased risk of merge conflicts, and poor support from popular tools. You could also suffer performance issues as the monorepo grows.
A monorepo’s transparency won’t be appropriate in all scenarios. If you’re in a tightly regulated environment, you might need to use individual repositories so that you can enforce appropriate access controls. Monorepos also increase the risk if an employee’s device is lost or stolen. People with physical access would be able to view all of your code instead of just the projects relevant to that individual.
The decision to use a monorepo should be based on your own projects, their cross-project dependencies, and your team members. Don’t look to the big tech companies and expect to observe their successes in your own projects. There’s more to good code culture than the type of repository you use. Monorepos make the most sense where people are already freely collaborating cross-project of their own volition.