Learn to Scale React Development with Nx

Learn to Scale React Development with Nx

·

19 min read

Create a new empty Nx Workspace

Nx is a set of extensible dev tools for monorepos, which helps you develop like Google, Facebook, and Microsoft. Nx site

We can create and configure a new NX workspace with the command

npx create-nx-workspace <workspace name>

Running this command will give you a set of options that allows you to create the best workspace for your needs.

  • empty
  • oss
  • web components
  • angular
  • angular-nest
  • react
  • react-express

Then it will also ask you about which CLI should be activated in your workspace

  • NX - used for all workspaces
  • Angular CLI - used for Angular workspaces

Finally, you get asked if you want to use the NX cloud, this is the commercial version of NX. There is a free tier which provides better performance and distribution of cache between co-workers.

The Nx workspace

The workspace contains different files and folders.

  • Apps folder - contains all the applications we want to host
  • Libs folder - contains all the code, usually, applications will link to code inside this folder
  • A single node_modules folder for the workspace
  • The package.json serves as the global file for packages for the whole workspace
  • workspace.json contains all the configuration of your projects and how to build it
  • nx.json contains the configuration of the workspace

Generate a new React app with Nx

When using the nx list command, we can see all the plugins available to be used in our workspace. We can use plugins for next, nest, node, angular, etc. There are also community plugins that give us more options like support for vue or go - just to name two of the options.

Creating a React App

We can install @nrwl/react to get a react app started.

Using yarn

yarn add @nrwl/react

Using npm

npm i @nrwl/react

Now we can run yarn nx list @nrwl/react to see all the possibilities that this plugin gives us.

  • Create an application
  • Create a library
  • Create a component
  • Create a redux slice for a project
  • Set up storybook
  • Generate storybook story
  • Create a cypress spec

Note: Hanii is using yarn on the course, so these notes will only contain the yarn version of running the commands, but you can use npm instead.

Let's create the React Application

By running the command yarn nx generate @nrwl/react:application <name> you can also use yarn nx g @nrwl/react:application <name> for short.

When running this command, Nx will ask you what kind of stylesheet format you want to use - for example, CSS, SASS, Stylus, etc.

We can also append the --dry-run flag to the previous command to allow us to see all the files that this command would create.

When creating a React Application there are a few configuration files that are automatically created for us.

  • .eslintrc
  • babel.config.json
  • jest.config.js

Generate new Projects for Nx with Nx Console

In Nx, you will usually use the command line to generate new things.

You can install the Visual Studio Code extension [Nx Console]

(nx.dev/latest/react/cli/console#nx-console) from the VS Code extension tab

This extension allows you to have a better view of your workspace, what projects you have, common Nx commands, and also run project commands.

Using Nx Console will help you save time looking for the commands available in Nx by using the CLI.

Running a React App in the Browser with Nx

Let's talk about the workspace.json - this file contains all the projects available in the workspace, current configuration and how you can run, build, test or anything that you can do in your projects.

{
  "version": 1,
  "projects": {},
  "cli": {},
  "schematics": {},
  "defaultProject": "store"
}

Inside projects we can see all the projects inside our workspace. Currently, we have store and store-e2e.

 "store": {
      "root": "apps/store",
      "sourceRoot": "apps/store/src",
      "projectType": "application",
      "schematics": {},
      "architect": {
        "build": {},
        "serve": {},
        "lint": {},
        "test": {}
      }
 }

If we open the store, we can see the basic information for the application, we also have this architect which contains all the targets that an app can run.

  • Build the app
  • Serve the app
  • Lint the app
  • Test the app

Running the React App

To running the React Application we need to serve the app. If we open serve we can see a few things:

{
  "serve": {
    "builder": "@nrwl/web:dev-server",
    "options": {
      "buildTarget": "store:build"
    },
    "configurations": {
      "production": {
        "buildTarget": "store:build:production"
      }
    }
  }
}
  • The builder entry is responsible for running the application.
  • The options allow you to pass options to the command, for example, to change a port.

Let's go into our command line and use the command to run the React Application.

yarn nx run <name of the project>:serve

So in our case we will run yarn nx run store:serve

Note: workspace.json contains a defaultProject, this allows us to run the command yarn start to run the serve command of the project set as default. Have a look at package.json.

Install and use external npm packages in an Nx Workspace

We can install npm packages in an Nx Workspace like we install any npm package.

Using yarn:

yarn add <package name>

Using npx:

npx install <package name>

The thing to keep in mind is that, since we have a global package.json file, the package that we are installing, will be available for the entire workspace.

Add Styling to a React app inside an Nx workspace

we have selected to use SASS for our stylesheet, so any style being placed inside the styles.scss file will be compiled for us.

There are a few ways you can manage your stylesheets.

  • You can import them into that styles.scss file
  • Update the styles array inside the build in the workspace.json file
    • Any file added to the array will be compiled at build time

Using the style flag

If you already know what sort of file extension you want to use for your styles, you can use the --style flag together with the npx generate command to specify a format.

For example to use Less you can run the command

yarn npx generate @nrwl/react:application --style less

Note: Nx will save your style options and will use that throughout the whole workspace.

Configure Assets for my React app in an Nx Workspace

Nx creates an assets folder inside our apps where we can serve assets such as images, sounds, videos, etc.

Any file inside this assets folder, will be available for us to link to by just using the relative path of the file. For example:

<img src="/assets/beans.png" />

Changing or adding folders to assets

{
  "version": 1,
  "projects": {
    "store": {
      "root": "apps/store",
      "sourceRoot": "apps/store/src",
      "projectType": "application",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@nrwl/web:build",
          "options": {
            "outputPath": "dist/apps/store",
            "index": "apps/store/src/index.html",
            "main": "apps/store/src/main.tsx",
            "polyfills": "apps/store/src/polyfills.ts",
            "tsConfig": "apps/store/tsconfig.app.json",
            "assets": ["apps/store/src/favicon.ico", "apps/store/src/assets"],
            "styles": ["apps/store/src/styles.scss"],
            "scripts": [],
            "webpackConfig": "@nrwl/react/plugins/webpack"
          },
        },
    },

Similar to the stylesheet, you can add more folders to be used as assets by opening your workspace.json and inside the build you can see an assets array. Add the path of the folder that you want to include and Nx will start serving the contents from that folder.

You can also copy assets by using the extended way of copying assets.

{
  "assets": [
    "apps/store/src/favicon.ico",
    "apps/store/src/assets",
    {
      "input": ".",
      "glob": "*.png",
      "output": ""
    }
  ]
}

Let's have a look at what each of these means:

  • input points to some folder location
  • glob pattern of what you want to copy - in the example we are copying only PNG files
  • output points to the output directory, where the asset should be copied

Create a Shared React Library in an Nx Workspace

"The real power of Nx comes when you have to scale your development across different teams and across different applications. Nx is actually designed to work in large monorepos where you have most of your applications or a subset of correlated applications within the same workspace."

Let's look at the difference between Applications and Library.

In essence, the applications living inside the apps folder will use different libraries, so they will bundle the code inside the libs folder.

The Library will contain code that we want to import from the apps folder. This is where all our components and other code will live and the amount of code inside the libs folder is always larger than the one in the apps folder.

Create a React Library

Similar to how we can create a new React Application, we will use the command line to create a new Library.

yarn nx g @nrwl/react:lib <name of library> --directory=<name of directory>

Since you can have different applications in your Nx workspace, it's a good idea to put new libraries inside a directory that contain the name of the application that will use this library.

This is why we used the --directory flag, to create a new library inside a store folder, since we will use this library on our store.

Generating a React Component

When we create a React Library, Nx will also generate a placeholder component for us. Let's see how you can generate a React component with the command line.

yarn nx g @nrwl/react:component <name of component> --project=<name of library>

Remember that we created a ui-shared library earlier. By using the --project flag, we can specify which library should this component be generated in. This flag takes the combination of the library path.

*Example:

yarn nx g @nrwl/react:component header --project=store-ui-shared

This command will create a header component inside the store and inside the ui-shared folder.

Note: You can go into workspace.json and inside projects you can see a store-ui-shared node created - this is what we use in the --projects flag.

After running the component generate command, Nx will ask if you want the component to be exported.

If you are using the component in the Application and is not an internal component to the library, then you should select yes.

Using the newly created component

Since we exported this component, we can open the tsconfig.base.json file and scroll down to paths to see the paths of each created component.


{
  "compileOnSave": false,
  "compilerOptions": {
    "rootDir": ".",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es2015",
    "module": "esnext",
    "typeRoots": ["node_modules/@types"],
    "lib": ["es2017", "dom"],
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "baseUrl": ".",
    "paths": {
      "@nxnothanii/store/ui-shared": ["libs/store/ui-shared/src/index.ts"]
    }
  },
  "exclude": ["node_modules", "tmp"]

You can see that we have @nxnothanii/store/ui-shared created inside paths. This is what we need to use to import our component.

import { Header } from @nxnothanii/store/ui-shared

Generate a TypeScript library in an Nx Workspace

To create a plain TypeScript library, we can run the command:

yarn nx g @nrwl/workspace:lib <name of library> --directory=<directory name>

Note: Since we are using a plain TypeScript, we don't need to use @nrwl/react plugin, we instead just generate a library inside the workspace with the command @nrwl/workspace:lib.

Similar to creating components, we can check how this TypeScript library is being exported by looking in the paths node inside the tsconfig.base.json file. Then we can use that to import the TypeScript library inside our Application.

import { formatRating } from '@nxnothanii/store/util-formatters'

Note: formatRating was just a function that we used in this new library that we created.

Use the Nx Dependency Graph to Visualize your Monorepo Structure

"Behind the scenes, Nx understands the structure of your workspace. It create that dependency graph that understands the relationship between applications that live up here and libraries down here, or even between the libraries."

We can launch the visual tool that draws the dependency graph with the command:

yarn nx dep-graph

Then you can go to http://127.0.0.1:4211 in your browser, to see and interact with the graph.

The visual tool starts empty, but you can select apps, projects and libraries by using the checkboxes. You can also click the Select All button to see a visual representation of your whole monorepo.

Good to know

A few things that are good to know about the graph representation of your project:

  • Applications are represented by squares
  • Libraries are represented by circles
  • The arrows show the libraries that an application imports

If you click on each element of the graph you can focus the view on that element to get a better understanding of what relationships each element has.

Create React Feature Libraries in an Nx Workspace

Let's see why you should keep the bulk of your code inside the libs folder.

  • Reusability of the code, you can use it on different apps.
  • Bounded context, you can group together some functionality and use it in an app.

    • This makes you think what should be private and what should be public.
    • Help speed compilation/testing/linting.

    we create a new library, but we are using a new flag --appProject. This flag allows us to generate a router component in that project and link it to the generated library.

yarn nx g @nrwl/react:lib future-game-detail --directory=store --appProject=store

In the example, we are creating the future-game-detail inside the store directory under libs and we are setting that router component.

Create an Express Backend API in an Nx Workspace

As we have seen before, we can install a various number of plugins in our Nx workspace.

Let's install Express to create an API for the project.

yarn add -D @nrwl/express

After installation of this plugin, we can now generate a new express application with the command:

yarn nx generate @nrwl/express:application <name>

You can also use useful flags on this command. For example, using the --frontendProject flag will set up that project as the frontend and Nx will automatically set up a proxy for us.

yarn add -D @nrwl/express api --frontendProject=store

This command will start a new application called api and set up store as our frontend project.

If we run this command, we can see that inside our store folder, a proxy.conf.json file was set up by Nx.

// content of proxy.conf.json
{
  "/api": {
    "target": "http://localhost:3333",
    "secure": false
  }
}

Now if we run our store application, the /api route will be forward to localhost:3333.

Use Run Commands to launch the API and App of an Nx Workspace

To have the two applications working (the frontend and the backend), we need to run two commands - one to run each application.

yarn run <app name>:serve

For example to run both the api and the store application we can run in one terminal:

yarn run api:serve

And in another terminal

yarn run store:serve

When we have both applications running we can now go to localhost:4200/api to get the api results.

Note: Since Nx created a proxy for us, the store application is running on port 4200 whilst the api one is running on a different port. We can access the api application, because of that proxy.

Running both applications with a single command

Nx allows us to run as many applications as we need with the command run-many.

yarn nx run-many --target=serve --projects=api,store --parallel=true

That's a lot of flags! Let's have a look at them:

  • --target is the type of command we want to run - we want to use serve to run both applications
  • --projects these are all the applications we want to run in the command, they are comma-separated
  • --parallel is used because we want to serve both projects together at the same time
    • If we don't use it, Nx would run the first project until terminated, before starting the next one

Creating a run command to launch both applications

Let's open the workspace.json, inside the store we can see that inside the architect node we have a few targets like build, serve, lint and test.

We can create a custom target that will allow us to start the api application together with the store. That way we won't rely on the run-many command.

{
  // Inside the architect node
  "serveAppAndApi": {
    "builder": "@nrwl/workspace:run-commands",
    "options": {
      "commands": [
        {
          "command": "nx run api:serve"
        },
        {
          "command": "nx run store:serve"
        }
      ]
    }
  }
}
  • Each command has to have a builder which has the implementation to run the job.
  • We can now choose options which are commands that we want to execute.

Note: run-commands will run the commands in parallel by default.

Now we can run our custom target with the command:

yarn nx run store:serveAppAndApi

"Run commands are very powerful because you can encapsulate whatever script you want to run within your workspace configuration, where all our scripts and run commands are."

Connect to an Express.js API from a React app in an Nx Workspace

Since we set up store as the frontend when we created our api application and Nx created that proxy for us, we can now use that url to fetch data straight from our backend.

So if we try to fetch /api/games, that request will use our proxy and fetch the data from out backend which is running on port 3333:

fetch('/api/games')
  .then((x) => x.json())
  .then((res) => {
      setState({
          ...state,
          data: res,
          loadingState: 'success'
      })
  })

Share and Reuse functionality with libraries in Nx

Similar to how we import shared libraries in our Applications, we can also do the same in our libraries inside the libs folder.

To use our formatRating we can import it as we did on the store application.

import { formatRating } from '@nxnothanii/store/util-formatter'

Share code between a React Frontend and Node.js Backend Application in Nx

Let's see how we can share code between applications and libraries.

We start by creating a new library called util-interfaces with the generate command.

yarn nx generate @nrwl/workspace:lib util-interfaces --directory=api

You can see that we are creating a new directory inside our libs folder because this library is related to our api application.

Then we can create a new Game interface inside the file api-util-interfaces.ts

export interface Game {
  id: string
  name: string
  image: string
  description: string
  price: number
  rating: number
}

Note: We renamed the file game.ts

This interface represents the structure of a game that gets exposed by our api.

Since we changed the name of the file, we need to update the export inside our index.ts file (libs/api/util-interfaces/src/index.ts)

export * from './lib/game'

We can know import our Game interface with:

import { Game } from '@nxnothanii/api/util-interfaces'

" having the backhand also in the same monorepo can bring a lot of advantages because, in this way, we cannot even share just simple types as we did in our very simple example here, but maybe even some business logic that needs to be repeated on the frontend side as well as on the backend side once the data arrives."

Use Storybook to Develop React Components in Nx

Nx comes with a dedicated packaged for storybook - @nrwl/storybook that adds a series of useful code generators to add storybook to an existing React library.

Let's install the storybook package.

yarn add -D @nrwl/storybook

Now we can run the following command to create a Storybook for our shared components.

yarn nx generate @nrwl/react:storybook-configuration store-ui-share --configureCypress --generateStories

Note: Our Header component is inside the store-ui-share so this is the name of the product that we want to add the Storybook configuration.

Let's take a brief look at these flags.

  • --configureCypress set up a Cypress test configuration
  • --generateStories generate stories automatically for components inside the store-ui-share library

We can run Storybook with the command:

yarn nx run store-ui-shared:storybook

Storybook will be run on localhost:4400, we can go to this url in our browser and see all the available stories that were automatically generated for us.

Use Cypress and Storybook to test your React Components in Isolation

Since we created a storybook with the --configureCypress, we can run Cypress with the command:

yarn nx run store-ui-shared-e2e:e2e

Note: store-ui-shared-e2e was created because we added that --configureCypress flag.

We can use the --watch flag to the command that starts the Cypress test and allow us to see what is happening.

Run Jest tests for a React app with Nx

When we generate an Application or Library inside an Nx workspace, Nx will preconfigure Jest and will set some basic tests.

We can run our tests with the command:

yarn nx run store:test

If we run this command, the basic tests that were created will fail, because we have changed our application so we need to update our tests.

If you want to have the same experience with jest by using the jest watcher, if you pass the --watch flag to the previous command.

yarn nx run store:test --watch

Building your React app with Nx

As we have been doing throughout the course, we can open the workspace.json file and see what targets are available to us.

To build our project we can run the command:

yarn nx run store:build --configuration=production

Alternatively, you can run the nx build command that is set up for us by Nx

yarn nx build store --configuration=production

You can see that we production to the --configuration flag, that tells Nx that you are building for production.

After running the command we can see that the production-ready build was created on dist/app/store.

Linting a React app with Nx

Nx also generates a .eslintrc files when we start a new workspace - this file contains some linting rules that are set up for us.

Since the lint rules were set up for us we can run the lint command:

yarn nx run store:lint

This will lint our store applications for potential issues.

Scale CI runs with Nx Affected Commands

One of the issues with working with monorepos is that the more our workspace grows, the slower our CI will be.

Also, you probably don't want to build the whole workspace if you have changed a few files in a part of the workspace like an application.

Nx provides an affected command that helps us build only the things that have changed.

yarn nx affected:<target> --base=<branch - defaults master>

We can have a visual representation of the changes done on our workspace with the command:

yarn nx affected:dep-graph --base=16-adjust-jest-test

Let's say that on our CI we want to run all the affected tests and linting for the changes that we are trying to merge.

We can run the test target for all affected changes.

yarn nx affected:test --base=16-adjust-jest-test

Then we can run the lint target for all the affected changes.

yarn nx affected:lint --base=16-adjust-jest-test

" As you can imagine, this is really powerful especially in your CI configuration. For instance, to run all the test cases, you would just use the test target here on your CI configuration to run only those projects that have been touched by a given PR."

Speed up with Nx computation Caching

"Nx shines when it comes to scale your monorepo to the masses."

Nx uses a computation cache. This means that whenever we run a command, Nx will cache it so if we run the same command again it will get the results from the cache and return it.

This is useful because in large monorepos using cache will save us a large chunk of time. Also, if we haven't touched a particular part of our workspace there is no point of running a test for example on that project since nothing has changed.

Nx also has a flag that allows you to skip the cache when running the affected command.

yarn nx affected:test --all --skip-nx-cache

Nx cloud

When we created our Nx workspace, we got prompted if we wanted to use Nx cloud.

Nx cloud brings benefits when dealing with cache because it will be shared among all the users of our workspace. So our coworkers can benefit from it.

References