If you haven’t used Nx, you way want to watch this 10-minute video before reading the rest of the post:
Now, let’s create a new Nx workspace.
npx create-nx-workspace happyorg --preset=react-express --appName=tickets
The workspace has two applications
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).
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.
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:
We will see that Nx figured out that both
tickets are affected by this change:
We can test what is affected, like this:
nx affected:test --parallel # runs tests for
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 testslibs/ 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.