Andrew Evans Husband, engineer, FOSS contributor, and manager at CapTech. Follow me at rhythmandbinary.com and andrewevans.dev.

Building an application with React and Nx

6 min read 1775

Building Application React Nx

Build tools are valuable assets for any web developer. Currently, many great build tools are available for all of the leading JavaScript frameworks. Nx, a build framework that allows you to build monorepos and scaffold full applications in place, is one such example that has recently been growing in popularity.

Nx automates many of the steps that developers must typically perform manually when building applications. It includes features like computation caching, dependency graphs, and build automation, and it also includes a plugin integration with Cypress.

In this post, we’ll walk through using Nx with React to build a simple to-do list application. To follow along, check out my sample project on GitHub.

Let’s get started!

Setting up a workspace

To get started, you’ll need to create a Nx workspace. You can use npx to do so by running the following code:

npx [email protected]

You can also install Nx globally.

When the CLI runs, it creates a workspace, which is basically a container for your project. When we’re finished, this container will become a monorepo.

Nx Cli Runs Container Monorepo

The CLI will ask you a series of questions before scaffolding out the main skeleton of your project. We’re building a React application, so the file structure looks like the code block below:

├── README.md
├── apps
│   ├── first-project
│   └── first-project-e2e
├── babel.config.json
├── jest.config.js
├── jest.preset.js
├── libs
├── nx.json
├── package-lock.json
├── package.json
├── tools
│   ├── generators
│   └── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json

The file structure includes an apps directory that has two folders. One is for the project itself, and the other is for running end-to-end tests with Cypress.

We made a custom demo for .
No really. Click here to check it out.

Now that you have a scaffolded application, you can run it using npx nx serve first-project.

You’ll receive the following output:

Nx First Project Homepage

Creating an API

Let’s create an API alongside our React project that we can use for performing any REST calls. One cool feature of Nx is its ability to add an API to your project in a single command.

We’ll create an API using Express. First, you need to install the express plugin with the Nx CLI:

npm install --save-dev @nrwl/express

Now, you can create the API with the following command:

npx nx g @nrwl/express:app api --frontendProject=first-project

The folder structure should look like the code below:

├── README.md
├── apps
│   ├── api
│   ├── first-project
│   └── first-project-e2e
├── babel.config.json
├── jest.config.js
├── jest.preset.js
├── libs
├── nx.json
├── package-lock.json
├── package.json
├── tools
│   ├── generators
│   └── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json

Scaffolding the application

Now that we have the building blocks in place, let’s look at what we’ve built so far! If you look in the apps folder, you’ll see the following code:

├── api
│   ├── jest.config.js
│   ├── src
│   │   ├── app
│   │   ├── assets
│   │   ├── environments
│   │   └── main.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   └── tsconfig.spec.json
├── first-project
│   ├── jest.config.js
│   ├── proxy.conf.json
│   ├── src
│   │   ├── app
│   │   ├── assets
│   │   ├── environments
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   ├── main.tsx
│   │   ├── polyfills.ts
│   │   └── styles.scss
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   └── tsconfig.spec.json
└── first-project-e2e
    ├── cypress.json
    ├── src
    │   ├── fixtures
    │   ├── integration
    │   ├── plugins
    │   └── support
    ├── tsconfig.e2e.json
    └── tsconfig.json

The api project is a full Express app scaffolded with main.ts, an entrypoint file:

/**
 * This is not a production server yet!
 * This is only a minimal backend to get started.
 */
import * as express from 'express';
const app = express();
app.get('/api', (req, res) => {
  res.send({ message: 'Welcome to api!' });
});
const port = process.env.port || 3333;
const server = app.listen(port, () => {
  console.log(`Listening at http://localhost:${port}/api`);
});
server.on('error', console.error);

Go in the first-project folder. You’ll see the React app built with the traditional folder structure commonly seen in assets, styles, index, main, and app files:

import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import App from './app/app';
ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  document.getElementById('root')
);

Finally, if you look in the first-project-e2e folder, you’ll see the Cypress project with the traditional structure of any Cypress test suite:

├── cypress.json
├── src
│   ├── fixtures
│   ├── integration
│   ├── plugins
│   └── support
├── tsconfig.e2e.json
└── tsconfig.json

Calling the API

So far, we’ve built a scaffolded app. Now, let’s add a feature that shows the CLI in action! I’m going to build a simple to-do application with React and Nx.

First, copy the following code into the apps/first-project/src/app/app.tsx folder:

import React, { useState } from 'react';
interface Todo {
  title: string;
}
export const App = () => {
  const [todos, setTodos] = useState<Todo[]>([
    { title: 'Todo 1' },
    { title: 'Todo 2' },
  ]);
  function addTodo() {
    setTodos([
      ...todos,
      {
        title: `New todo ${Math.floor(Math.random() * 1000)}`,
      },
    ]);
  }
  return (
    <>
      <h1>Todos</h1>
      <ul>
        {todos.map((t) => (
          <li className={'todo'}>{t.title}</li>
        ))}
      </ul>
      <button id={'add-todo'} onClick={addTodo}>
        Add Todo
      </button>
    </>
  );
};
export default App;

If you run npm run start from the project root, you’ll see the following:

Npm Run Start Project Root

So far, so good! We can improve our code by integrating a call to the API project. First, create a file called apps/api/src/app/todos.ts and add the following code to it:

import { Express } from 'express';

interface Todo {
  title: string;
}

const todos: Todo[] = [{ title: 'Todo 1' }, { title: 'Todo 2' }];

export function addTodoRoutes(app: Express) {
  app.get('/api/todos', (req, resp) => resp.send(todos));
  app.post('/api/addTodo', (req, resp) => {
    const newTodo = {
      title: `New todo ${Math.floor(Math.random() * 1000)}`,
    };
    todos.push(newTodo);
    resp.send(newTodo);
  });
}

The code above creates many of the routes we’ll need for our API. Now, we’ll register the routes with our API by modifying the apps/api/src/main.ts file to look like the following code block:

import * as express from 'express';
import { addTodoRoutes } from './app/todos';
const app = express();
app.get('/api', (req, res) => {
  res.send({ message: 'Welcome to api!' });
});
addTodoRoutes(app);
const port = process.env.port || 3333;
const server = app.listen(port, () => {
  console.log(`Listening at http://localhost:${port}/api`);
});
server.on('error', console.error);

Run npx nx serve api and hit http://localhost:3333/api/todos. You’ll see the following:

[{"title":"Todo 1"},{"title":"Todo 2"}]

Now, let’s call the API from our app. We’ll set up a proxy so that the API calls from our React app call the API directly.

Look at the workspace.json file that is at the project root, where you’ll find configuration for your entire application.

Next, we need to locate the proxyConfig in the serve target of our first-project React app, which should look like the following code block:

{
  "serve": {
    "builder": "@nrwl/web:dev-server",
    "options": {
      "buildTarget": "first-project:build",
      "proxyConfig": "apps/first-project/proxy.conf.json"
    },
    "configurations": {
      "production": {
        "buildTarget": "first-projects:build:production"
      }
    }
  }
}

Open the file at the proxyConifg setting at apps/first-project/proxy.conf.json and add the following code. It will proxy all API calls to call your API project:

{
  "/api": {
    "target": "http://localhost:3333",
    "secure": false
  }
}

Now, if we modify the main.ts file in the first-rproject folder, we can call the API instead of using the local state that we had set up originally. Modify apps/first-project/src/app/app.tsx to look like the following code block:

import React, { useEffect, useState } from 'react';

interface Todo {
  title: string;
}

const App = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

  useEffect(() => {
    fetch('/api/todos')
      .then((_) => _.json())
      .then(setTodos);
  }, []);

  function addTodo() {
    fetch('/api/addTodo', {
      method: 'POST',
      body: '',
    })
      .then((_) => _.json())
      .then((newTodo) => {
        setTodos([...todos, newTodo]);
      });
  }

  return (
    <>
      <h1>Todos</h1>
      <ul>
        {todos.map((t) => (
          <li className={'todo'}>{t.title}</li>
        ))}
      </ul>
      <button id={'add-todo'} onClick={addTodo}>
        Add Todo
      </button>
    </>
  );
};

export default App;

Now, our React app is calling the API instead of relying on the local state.

Let’s run the API and the frontendProject! You can use npx nx serve first-project and npx nx serve api. You can also use an npm package, like Concurrently and start-server-and-test, to run the API automatically using scripts.

Nx Npm Run Api Frontendproject Page

Testing with Cypress

As mentioned, Nx comes with Cypress tests built in when the app is scaffolded.

To see testing in action, modify the test file at apps/first-project-e2e/src/support/app.po.ts to include the following:

export const getTodos = () => cy.get('li.todo');
export const getAddTodoButton = () => cy.get('button#add-todo');

Now, modify the file at apps/first-project-e2e/src/integration/app.spec.ts to look like the following code:

import { getAddTodoButton, getTodos } from '../support/app.po';

describe('TodoApps', () => {
  beforeEach(() => cy.visit('/'));

  it('should display todos', () => {
    getTodos().should((t) => expect(t.length).equal(2));
    getAddTodoButton().click();
    getTodos().should((t) => expect(t.length).equal(3));
  });
});

Run npx nx e2e first-project-e2e --watch to see your tests in action!

Additional Nx features

The Nx CLI has many features beyond what we’ve covered so far. One of the most useful is the ability to generate a dependency graph for your project, which simplifies complex projects through data visualizations. Our simple to-do application doesn’t need this, but it’s cool to see all the same!

Start by running the following code:

npx nx dep-graph

You’ll receive an output that looks like the image below:

Generate Dependency Graph Nx

Another really cool feature is Nx’s ability to detect changes and only rebuild parts that are impacted by the latest modifications to your project. Here are some sample commands that utilize this feature, sourced from the the Nx documentation.

nx affected:apps # prints affected apps
nx affected:libs # prints affected libs
nx affected:build # builds affected apps and libs
nx affected:lint # lints affected apps and libs
nx affected:test # tests affected apps and libs
nx affected:e2e # e2e tests affected apps

Finally, Nx allows you to create libraries and computation caching for performance. I highly recommend reading more on these and other features!

Next steps

In this tutorial, we covered the basics of using React and Nx by scaffolding a simple to-do application. The next step for our project would be to build the assets and deploy them. Nx can automate builds for small projects, and also scale to larger projects with enterprise applications.

I hope you’ve enjoyed reading this post! I hope it has inspired you to check out Nx and use it with React. I definitely recommend checking out the Nx documentation and reading some of their guides for more information.

Follow me on andrewevans.dev and connect with me on Twitter at @AndrewEvans0102.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Andrew Evans Husband, engineer, FOSS contributor, and manager at CapTech. Follow me at rhythmandbinary.com and andrewevans.dev.

Leave a Reply