Why you should switch from Lerna to Nx - Nrwl

September 03, 2019 0 Comments

Why you should switch from Lerna to Nx - Nrwl



If you haven’t used Nx, you way want to watch this 10-minute video before reading the rest of the post:

Creating Applications

Now, let’s create a new Nx workspace.

npx create-nx-workspace happyorg --preset=react-express --appName=tickets

The workspace has two applications tickets and api, a suite of e2e tests, and a library api-interfaces, which the applications share.

We can run the applications as follows:

  • nx serve api
  • nx serve tickets

If we change anything in api-interfaces both the api and the frontend will reflect the change.

The example shows that Nx provides first class support not just for libraries (packages to be used in other packages), but also for applications. We can build/serve/test them. Applications often work in tandem, and Nx helps coordinate that. Application can often run in different modes, and Nx provides a way to manage those (e.g., nx serve tickets and nx serve tickets --configuration=production).

Creating Libraries

Most libraries in an org monorepo are consumed by other libraries and applications in the same repo. This means they are lightweight, which in turn means we can create many more of them.

Creating every single library from scratch is tedious and error prone. That’s why Nx has first-class plugins for a few major frameworks, so we can create plain ts/js libraries, angular libraries, react libraries with ease. This is how it is done:

nx g @nrwl/react:lib ticket-list

Right after we ran the command, we can import the newly created library into an application without any configuration:

Having such generic capabilities is good, but not enough. Every organization has specific needs. With Nx, we can define our unique set of code generators we can use to promote best practices within our organization.

Enforcing Ownership

Let’s say we have a different team that added another application into the workspace.

yarn add @nrwl/angular
nx g @nrwl/angular:app agent

We need to make sure that the agent and tickets teams can develop their applications without stepping on each others toes.

Adding a CODEOWNERS file solves a part of the problem, so the folks from the agent team cannot change the tickets app without the tickets team knowing and vise versa. That’s good but not enough.

To see why, let’s imagine the api-interfaces library is owned by the tickets team, and they want to be able to change it freely. As it stands right now, nothing prevents the agent team from doing this:

This is a problem.

The CODEOWNERS file tells us who owns every node of the dependency graph, but it doesn’t tell us what edges are allowed. To solve this, Nx provide a generic mechanism for defining rules that specify how libraries can depend on each other. So the import above will result in this error message:

Read more about it here.

Building Only What is Affected

Rebuilding and retesting everything on every commit does not scale beyond a handful of projects.

Nx can figure out the dependency graph of the workspace by analyzing the code. It then can use the graph to only rebuild and retest what is affected.

For instance, if we change the api-interfaces library and run:

nx affected:dep-graph

We will see that Nx figured out that both api and tickets are affected by this change:

We can test what is affected, like this:

nx affected:test --parallel # runs tests for api-interfaces, api, and tickets in parallel.

We can also easily distribute the tests across a grid of machines on CI.

Read more about it here.


Even our tiny workspace already has a bunch of technologies in it.

apps/ agent/ - Angular app agent-e2e/ - Cypress tests api/ - Express app tickets/ - React app

tickets-e2e/ - Cypress tests

libs/ api-interfaces/ - simple TypeScript lib ticket-list/ - a React lib


Even though Nx uses Webpack, Cypress, Jest, ESLint under the hood, it provides a unified command-line interface, so we can interact with all the projects in the same way.

nx lint agent will use tslint, while nx lint tickets will use eslint. nx serve api will start the express app, and nx serve tickets will start webpack dev server. Each of those commands can have a completely different implementation with different flags. So we don’t lose any flexibility, we merely standardize how we invoke the commands.

This is what allows nx affected to work. Running nx affected:lint will relint the affected libraries and applications and will use the appropriate linter for each project. Similarly, nx affected:test will retest what is affected and will use the appropriate test runner where needed.

It also helps with developer mobility. One of the benefits of a monorepo is that folks can reuse each other’s code and contribute to each other’s projects. Being able to discover what commands a project has, and how to serve/test/build it, is key for enabling it.

Tag cloud