Skip to content

thdk/nx-demo

Repository files navigation

Nx demo

An introduction to Nx.

What we are building

A books library app.

Technologies

And...

But note that we are never restricted to any of these choices. We can start any new project in this repo with any of the supported tooling / frameworks.

The above tools are listed so that we can make consistent choices. Whenever there is a reason for it, please pick another that fits the job better.

project-graph

Create nx workspace

npx create-nx-workspace@latest nx-demo

Need to install the following packages:
[email protected]
Ok to proceed? (y)


 NX   Let's create a new workspace [https://nx.dev/getting-started/intro]

✔ Which stack do you want to use? · none
✔ Package-based monorepo, integrated monorepo, or standalone project? · integrated
✔ Which CI provider would you like to use? · github

 NX   Creating your v19.6.4 workspace.

✔ Installing dependencies with npm
✔ Successfully created the workspace: nx-demo.
✔ Nx Cloud has been set up successfully
✔ CI workflow has been generated successfully

—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————


 NX   Nx CLI is not installed globally.

This means that you will have to use "npx nx" to execute commands in the workspace.
Run "npm i -g nx" to be able to execute command directly.


 NX   Your CI setup is almost complete.

Finish it by visiting: https://cloud.nx.app/connect/4bBQtKTPG8



—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————


 NX   First time using Nx? Check out this interactive Nx tutorial.

https://nx.dev/getting-started/tutorials/npm-workspaces-tutorial

Package-based monorepo, integrated monorepo, or standalone project? · integrated

cd nx-demo

Integrated vs package based vs standalone

Nx cloud

Install plugins

See a list of official packages

npx nx add @nx/remix
npx nx add @nx/node
npx nx add @nx/nest

Generate applications

npx nx g @nx/remix:application \
  --directory apps/remix-app \
  --name remix-app \
  --projectNameAndRootFormat as-provided \
  --unitTestRunner vitest \
  --e2eTestRunner playwright

Navigate the nx workspace

npx nx show project remix-app

nx run [project][:target][:configuration]

npx nx run remix-app:dev

Generate libraries

Publishable

If you are planning to publish your package to a registry, you should use both --publishable and --importPath when generating the library. See also Nx docs for publishable libraries.

# Example publishable library
npx nx g lib books-api-client \
  --publishable \
  --importPath @nx-demo/books-api-client \
  --directory libs/books-api-client \
  --projectNameAndRootFormat as-provided \
  --unitTestRunner vitest \
  --bundler vite
# Example non publishable library
npx nx g lib books-api-client \
  --directory libs/books-api-client \
  --projectNameAndRootFormat as-provided \
  --unitTestRunner vitest \
  --bundler vite

Buildable

You can choose for either buildable or non buildable libraries.

I almost always go for buildable, since it doesn't bring any overhead for generating the required tooling, and nx will keep it up to date whenever a migration is required. It might be overkill for smaller projects, but it will allow your repo to scale without ever have to worry about it.

# use vite / vitest when prompted
npx nx g @nx/js:lib api-client

Workspace tools

Everyone is free how to organise their monorepo.

Move a project

Move api-client from root folder to subfolder in libs folder.

# We created the api-client project in the root of our repo
# instead of in the `libs` folder. Let's move it.
npx nx generate @nx/workspace:move \
  --project api-client \
  --destination libs/books-api-client \
  --newProjectName books-api-client \
  --projectNameAndRootFormat as-provided

Override inferred targets

npx nx show project remix-app

Copy the target and paste in in apps/remix-app/project.json under 'targets'

  "targets": {
    "dev": {
      "options": {
        "cwd": "apps/remix-app",
        "command": "remix dev --manual"
      },
      "executor": "nx:run-commands",
      "configurations": {},
      "parallelism": true
    }
  }

Embrace .env files

Nx can handle multiple .env files where one can add project defaults in a .env file. This file can be commited to source control. .env files should never contain secrets (or be encrypted).

For more details about how these files are process see: https://nx.dev/recipes/tips-n-tricks/define-environment-variables#set-environment-variables

@nx/dependency-checks

Since all our dependencies are managed by the root package.json file, it would be hard to manually keep track of which project really needs which dependencies. Luckly this is something we do not have to worry about since nx provides us with a eslint rule to automatically detect the dependencies of a project and update the projects package.json for us.

Running linting on this repo currently shows:

../nx-demo/libs/books-api-contracts/package.json
        4:3  error  The "books-api-contracts" project uses the following packages, but they are missing from "dependencies":
          - zod  @nx/dependency-checks

      ✖ 1 problem (1 error, 0 warnings)
        1 error and 0 warnings potentially fixable with the `--fix` option.

All we need to do is:

npx nx run-many lint -- --fix

Publishing

Use the setup-verdaccio generator from @nx/js to configure a local npm registry to test versioning / publising.

npx nx generate setup-verdaccio

Before you start the registry, adjust the configuration in project.json.

    "local-registry": {
      "executor": "@nx/js:verdaccio",
      "options": {
        "port": 4873,
        "config": ".verdaccio/config.yml",
        "storage": "tmp/local-registry/storage",
        "//": "default location is user, but let's keep all our config close to it's origin",
        "location": "project",
        "//": "only use the local registry for our own packages",
        "scopes": ["@nx-demo"]
      }
    }

Start local verdaccio registry

npx nx run local-registry

Versioning

npx nx add @nx/js

See release section in nx.json for configuration settings.

Note that you can also use nx release in any non Nx monorepo.

nx release actually wraps tree sub commands

  • nx release version
  • nx release changelog
  • nx release publish

Each of these commands can be run seperately (and also programatically if you feel the need to customize it more or implement it in other tooling).

npx nx release

# other scripts
## First ever release in the repo
npx nx release --first-release

## Useful for ci, won't prompt for publish confirmation
npx nx release --yes

Use nx to version both packages and applications

This is where things become more interesting. NX is mainly focussed on CI. But we could also leverage the versioning feature to version applications as well and use this version for tagging build artifacts and deploying specific versions.

To do this we need to differentiate packages from applications. One way to differentiate would be to treat all projects in the apps folder as an application. However, this folder might also contain other projects that do not need to be versioned such as my-app-e2e.

Therefor I suggest to use project tags. How you tag them is totally up to you but here I'll use type:package and type:app.

Another thing we need to do is seperate the publish phase from the version and changelog phase.

For versioning and changelog generation, both packages and apps will be treated the same. For publishing, obviously we only do this for packages.

Note, it's always a good idea to add "private": true to your package.json files of your applications to prevent any tool from publishing applications to a possibly public registry.

So eventually, our script will something like this:

# Bump versions and generate changelogs
npx nx release --skip-publish [--first-release]
# Publish packages
npx nx release publish --projects tag:type:package

Update: nx will never publish private packages. So you adding "private": true to all your apps package.json files is enough so you can still use the main release command. npx nx release [--first-release].

Version and release in CI

      name: build
      - run: npx nx affected -t lint test build docker-build

      # Version, generate changelogs and publish packages
      # Commit any changes and push them to the remote
      - name: versions
        if: github.ref_name == 'main'
        run: |
          npx nx release --yes --verbose
          git config --global user.name 'Github Action'
          git config --global user.email '[email protected]'
          git push --follow-tags
        # I'm using github packages as npm package registry, see the `.npmrc` file for configuration
        env:
          THDK_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Dependency management

Single version (Nx) vs Independently Maintained Dependencies (Any other monorepo)

https://nx.dev/concepts/decisions/dependency-management

Project organisation and best practices

Project types

https://nx.dev/concepts/decisions/project-dependency-rules

  • ui
  • data-access
  • feature
  • util

Dependency rules can be enforced based on Nx project tags using @nx/enforce-module-boundaries eslint rule.

# Rename remix-app to books-app
npx nx generate @nx/workspace:move --project remix-app --destination apps/books-app --projectNameAndRootFormat as-provided --newProjectName books-app

# Rename remix-app-e2e to books-app-e2e
npx nx generate @nx/workspace:move --project remix-app-e2e --destination apps/books-app-e2e --projectNameAndRootFormat as-provided --newProjectName books-app-e2e

# Rename nest-api-e2e to books-api-e2e
npx nx generate @nx/workspace:move --project nest-api-e2e --destination apps/books-api-e2e --projectNameAndRootFormat as-provided --newProjectName books-api-e2e

Deploy

Setup docker generator

Nx can generate a docker file a build-docker target for any node project.

npx nx g @nx/node:setup-docker \
  --project books-api \
  --outputPath dist/apps/books-api
docker run -it --rm --init -p 3000:3000 books-api

Add target docker-build in ci

- name:
  run: npx nx affected -t lint test build docker-build