Sometimes one app just isn't enough. In order to reach all potential users, your product may need not just a website, but mobile apps for Android and iOS as well - can you afford to develop all three apps, however? Maybe your user-facing app also requires a separate back-office, which shares some of the same business logic? Or perhaps you have multiple products, and don't want to waste resources by building each one from scratch and maintaining them separately? In cases such as these, choosing the right technical solution is critical.
A modern approach to these problems is to use a monorepo. Instead of storing source code in a separate repository for each project, multiple projects are stored in a single shared repository.
A monorepo makes it much easier to share code for common functionality across multiple projects. It also helps centralize build/release automation, development tooling and dependency versions. For these (and other) reasons, monorepos have become the standard at large companies like Google, Microsoft and Facebook.
What you can build with an Nx monorepo
Nx provides an open-source monorepo toolkit. It offers powerful tooling for development based on web technologies, including an extension of the Angular framework. In other words, Nx supports building any kind of app that uses the web technology stack. Nowadays, that applies to much more than simple websites.
By default, Angular builds single-page applications. SPAs are web apps that offer a faster and more interactive user experience than traditional multi-page applications, while still being distributed on the web (thus requiring no installation, only a link).
If SEO is a requirement, Angular can be combined with Angular Universal to implement server-side rendering - optimizing your website for web crawlers (e.g. Google's), as well as loading faster on first page visit.
Traditionally, mobile apps had to be developed separately for both Android and iOS (typically by different development teams, since each platform has its own tech stack). A modern alternative is to use a framework for hybrid app development, where apps for both platforms can be built from a single shared codebase. Ionic is one such framework, with first-class support for Angular - enabling developers to build a native app for both Android and iOS at the same time, in much the same way as a regular Angular app.
Angular can also be used to build Progressive Web Apps, i.e. web apps that also work much like native apps on mobile devices. The advantage of PWAs is that a single app functions as both a website and native app, resulting in a much faster time to market.
Benefits of using a monorepo
A monorepo can make development more efficient both technologically and from a process management perspective. Here are some of the main advantages we noticed on projects we've developed.
Code reuse is the most obvious benefit, and it is a significant one. An important principle in software development is not to reinvent the wheel - instead, solve the problem once and reuse that solution elsewhere.
Nx organizes code into libraries that can be reused across multiple apps. Shared libraries help the development team test, document and maintain a common solution in one place. Sticking to this approach will help reduce maintenance costs in the long term, since spending the time to do something right avoids the need to do it twice.
Shared CI/CD pipeline
Continuous integration and continuous delivery are important tools in modern software development - they help significantly speed up development and improve code quality at the same time. CI/CD can also go a long way to smoothing out communication between customers and developers, as they both get continuous access to an up-to-date version of the staged product.
Automation for tests, builds and releases can take some time to set up correctly. An advantage of a monorepo is that you only need to configure CI/CD once, and then that same pipeline is used for all apps in that repository - even those added later down the line.
It commonly takes some time for new developers to be fully onboarded when starting on a new project. One of their initial hurdles is having to get familiar with a whole new codebase, which may well use different coding practices, technologies or development pipelines to what they were used to previously.
We have found that this problem is significantly reduced when working in a monorepo, however. When a developer switches context from one monorepo app to another, they're already used to the codebase, pipeline and development technologies (as well as existing libraries for common solutions). Therefore, it takes much less time for them to get up and running (in our experience, it can be a matter of hours instead of the days/weeks required otherwise).
A fast and effective onboarding process offers a significant advantage in agile development, giving you much more flexibility in scaling your development budget up or down based on demand. For example, when one app has an important deadline coming up, developers can be quickly transferred from another app with lower priority at this time.
Increased code quality
The importance of code quality is often underestimated, since it can be tempting to take shortcuts which seem like they'll speed up development in the short term. However, doing this often (particularly without allowing developers to perform regular maintenance) will always end up costing you long-term, as the codebase becomes harder and harder to maintain (with bug fixes and new features taking longer than before).
Code maintainability is especially important in a monorepo due to the scale of the codebase. Because of this, Nx offers tools and guidelines for better code organization. Libraries don't only offer an elegant way of reusing code, they also force developers to decouple functionality in order to maintain a clean dependency graph (with automated dependency constraint checks) and build apps as a composition of isolated modules (maintained, documented and unit tested in one place). In short, a monorepo architecture helps improve code structure and promote best practices.
A monorepo also comes with increased visibility of changes. When a technical solution is spread across many different repositories (maintained and versioned separately), making big changes can be a slow and delicate process, since it's difficult to determine what effects a change can have on another codebase. This becomes much simpler in a single monorepo (Nx even detects affected apps and libraries automatically), enabling developers to make large scale changes with much more confidence.
Another benefit is the simplification of version management by enforcing a single version policy. This includes external dependencies (making it easier for all your apps to stay up-to-date), as well as internal libraries (avoiding the overhead of publishing packages).
A real world case study
In order to better illustrate what a monorepo solution might look like, I would like to share some technical background from one of our projects.
We had released a native iOS/Android app for another company, and were then tasked with building a website for the same brand (let's call it Cats). Around the same time, we also started planning a native app for another brand (Dogs) from the same company, with a tight deadline set for its release. Given that the technical requirements for the Dogs app were similar to the existing Cats app, the hope was to reuse its tech stack to achieve this fast delivery.
To scale up our technical solution, we started by migrating to an Nx monorepo, since we wanted the ability to efficiently reuse code. This monorepo consisted of 3 apps:
the existing native mobile app for Cats (built with Ionic & Angular),
a new native mobile app for Dogs (also built with Ionic & Angular),
a new server-side rendered website for Cats (built with Angular & Angular Universal).
We refactored our existing code into libraries and organized them by scope:
shared scope for generic utilities used by all apps,
Cats scope for business logic specific to that domain,
native scope for mobile-specific functionality.
The time we invested into the initial migration paid off, as the amount of code we were then able to reuse ended up saving us time overall. And after both new products were released, we had a technical setup that was also maintainable in the long term (with the possibility of adding more apps in the future).
If you're likely to need more than one application developed, and these apps share some common characteristics, you should consider whether a monorepo approach might be the best way to go. Investing a bit more time into the technical setup can save you money down the road. The main benefits include reusing what you've already built (do it once, do it right), as well as a centralized development pipeline, easier context-switching for developers and better code quality overall.