Imagine yourself working in a large organization with multiple teams, all working on an application that is part of the platform that the organization offers to its clients. You get a set of requirements and start implementing your features. After setting up your repository and all the tooling, you’re ready to go. All goes well during the development process and you’ve come to the end to deliver your hard work. After you’ve deployed your application on the platform, you’re introduced with a blank screen and a console log full of errors.
How can this happen? Your team followed the requirements and all the contracts that were defined to communicate with several services. Maybe these contracts got outdated in the meantime without you knowing it? Were there breaking changes introduced? Or does a library you are using locally differ from the ones used in production and isn’t compatible anymore? It’s really difficult for a large organization to manage all the separate projects and to maintain the overview of all of them. The complexity will increase and after a while the whole platform becomes unmanageable.
The problems mentioned above led large software companies to transition the management of their separate projects by bringing them together under one repository: the monorepo.
Inside the monorepo, you have all the applications working together while you’re developing your app.
You’ll get feedback immediately when the service you’re calling returns a different answer than expected or when the page you are navigating to is located on another path.
When you’re not sure how a service of the platform is working, you can just hop in the source code inside the repo to find out more about it.
Code sharing becomes a breeze, because all the code is available in one repository.
You don’t have to deal anymore with npm packages for shared code in your organization or with the separate configurations, pipelines and versions of the shared libs.
Because there is only one
package.json file, you can easily manage the versions and don’t have to deal with conflicting versions of the same dependency inside of your project.
But how can you start setting up a monorepo and what tools are required to do so? Having to manage all this code in one repo can be really tedious to set up but this is where Nx comes into play!
Nx provides you with the tools needed to help you develop in a monorepo. Based on their own experiences, the creators of Nx set up the best practices to structure your repository in a maintainable way. Nx is built on top of the Angular CLI, offering you a way to create workspaces and scaffolding out of your apps. Next to Angular apps, Nx does support creating React apps, NestJS apps, and more. You can even write your own builder to configure creating an app with the technology of your choice.
The setup of the repo has to happen only once, so you don’t have the hassle of doing it yourself each time when starting a new app.
Moreover, you won’t have the risk of teams using a whole set of different tools.
This makes it more convenient to switch projects as well, because the experience over the monorepo will be more consistent.
The versions of dependencies will be consistent over the whole repository because you only have one
package.json file, so you don’t have to deal anymore with version conflicts of libraries that are being used in different apps.
Starting with a new workspace is pretty straightforward. Let’s imagine we want to start a webshop where we will sell Nx-phones:
npx create-nx-workspace@latest nx-phone
Nx will scaffold our workspace.
The root of the project contains
apps where our applications will reside,
libs for code sharing between our apps and
tools that enables us to write our own schematics.
You will write most of your code inside the
Now that our workspace is set up, we can add apps to it. This can be done by different ways. We will focus now on using the Angular CLI. To add an Angular app, we first have to include the capability to create Angular apps with Nx. Afterwards, we can create our first app called “shop”.
ng add @nrwl/angular --defaults // Add Angular capability ng g @nrwl/angular:application shop // Add the shop app
You can choose the stylesheet format and automatically set up routing while doing so.
When creating a new app inside your workspace, Nx will generate both the
shop and a
shop-e2e folder for your end-to-end tests.
Running the following command using the Nx CLI will start up the shop app locally:
nx serve shop
libs folder is where your shared code will reside.
If your organisation has a component library or a design system, then that would be a good candidate to be placed under
You can also add all your code to the libs in separate modules like feature modules while using the app as a container.
Later on, you can use the features in the modules to compose your app.
If you want to take a look at the full example, please check out the nx-phone workspace here: https://github.com/DimiDeKerf/nx-phone
Nx supports modern tools that you’re familiar with, without the sometimes tedious way of setting them up.
You can easily use Jest and Cypress to cover your testing needs, with great CLI support.
The unit and end-to-end tests are both configured for the shop app that we’ve just created. They can be run by using
nx test shop and
nx e2e shop, respectively.
If you want to learn more about Jest or Cypress, be sure to check out the awesome blog posts that my colleagues have wrote:
How do you like to have your code formatted? There will be some differences between how others within your organization like to format their code. Just thinking about the merge conflicts that will arise when people use different indentations may already give you shivers.
Prettier comes included with Nx and is here to help you out with this. It’s an opinionated code formatter that integrates well with most editors. You’re tired of formatting your code before you want to commit something? Prettier can also run in a pre-commit hook, so your code will get formatted before your commit. More info can be found here.
What if you change something in your code? How can you be sure that your modification didn’t break another app? This is hard to do when your apps live in separate repositories but becomes much more convenient with the help of Nx. All your apps and libraries will be part of a dependency graph.
The dependency graph will give you an up-to-date version of all the applications and services, and the dependencies between them. It can also help you to get a better understanding of the architecture.
When new code is introduced or something has been refactored, Nx will be able to find out which dependencies are affected by your changes. This gives you the opportunity to only build the affected dependencies and only run the tests of those affected ones. For your build pipeline, that means that testing the affected apps can greatly improve the build time when dealing with large workspaces.
For example, if we edit something in the shop app, our dependency graph will look like this:
Targeting the affected code can be done using the affected command. For example, if you want to run the unit tests for the affected code:
Inside of your CI, you would like to compare the changes between your branch and master. This can be done by appending the two branches to the previous command:
nx affected:test --base=origin/master --head=your-branch
You may want to categorize libraries in different domains and limit the dependencies between those domains to improve maintainability.
Nx can help you with this, by restricting access between domains or by allowing the dependency flow to go only one way around.
To configure and manage these restrictions, you can apply tags in the
nx.json file for apps and libraries.
Be aware to also update the linting rules in
tslint.json to get feedback in your editor when violating those rules.
Full stack applications
With a monorepo, you can have both your frontend and your backend applications under the same repository. This enables you to share code between them. With Nx, you can do this by using libraries like mentioned before. These libraries will expose the code using a public API.
The code inside these libraries can be imported afterwards in your apps, without having to fetch them from a npm registry. Think about how easily you can share an interface between your frontend and backend, without having to break it on one side. Whenever something changes in the API, both sides will get updated so they will both remain in sync.
Having worked within a monorepo myself for the last months, I really appreciate the way it improved the daily workflow. It encourages me to write more reusable code and to keep features small. Other teams may have the same requirements, so code can be easily migrated to libs. It is important to manage the different features and libs though, before they turn up to be a massive dump of code that isn’t maintainable anymore. Switching between features is much easier and accessible, since I don’t have to check out another repository or getting familiar with the way of working of another team. Overall, I feel more connected with other developers and with the platform while building apps in the same repository.
If you’re interested in setting up a monorepo, be sure to check out Nx! It will help you tremendously with getting started.