4 Simple Steps to Streamline Your Development: Creating a Monorepo Setup with Nx & Yarn Workspaces.

Karthick Ragavendran
6 min readMar 6, 2023

With the rise of efficient build systems like Nx and Turborepo, creating and managing monorepos have become simpler.

Let's create a simple monorepo setup that has a NestJS project and NextJS project with shared linting, type-checking, formatting, and caching capabilities.

1. Setup yarn workspaces

Create a folder and open it with VSCode.

mkdir next-nest-monorepo

code next-nest-monorepo // Needs code command to be installed in path in vscode. Or manually open the folder.

yarn init -y

mkdir apps libs // Create folders for apps and libs

Update the package.json of the newly created nodejs project with the below.

{
"name": "next-nest-monorepo",
"version": "1.0.0",
"license": "MIT",
"private": "true",
"workspaces": {
"packages": [
"apps/*",
"libs/*"
]
}
}
  • "private": "true"indicates that the monorepo is private, and it won't be published to a public registry like npm.
  • "workspaces": {"packages": ["apps/*", “libs/*”]} specifies the location of the packages in the monorepo. Here, the "apps/*" pattern indicates that all packages in the apps directory should be included as workspaces. Workspaces are a Yarn feature that allows you to manage multiple packages in a single repository.

Git

Initialize and do the first commit.

git init

// Create a .gitignore
echo "node_modules \nbuild \ndist \n.next \n.env" >> .gitignore

git add .
git commit -m 'initial commit'

Create projects in “apps/*”

Create an apps directory and create NextJS and NestJS applications inside it.

// Create apps directory.
mkdir apps
cd apps

// Next
yarn create next-app web --ts

// Nest
npx nest new api --package-manager=yarn

Some fixes

Now we have a yarn workspace setup with both our backend and frontend projects inside it. I had to address a few problems.

If you face issues working with nestjs in a monorepo, you may need to avoid adding nestjs dependencies to the root node_modules. You can update the workspaces in the package.json as below.

"workspaces": {
"packages": [
"apps/*"
],
"nohoist": [
"**/@nestjs",
"**/@nestjs/**"
]
}

“nohoist” enables workspaces to consume 3rd-party libraries not yet compatible with its hoisting scheme. The idea is to disable the selected modules from being hoisted to the project root. They were placed in the actual (child) project instead, just like in a standalone, non-workspaces, project.

I also found that the NestJS project creates a separate .git when we initialize it. Remove it.

cd apps/api
rm -rf .git

2. Setup Nx: The build system

We are going to use Nx as the build system for our yarn monorepo.

npx add-nx-to-monorepo

Answer the questionnaire. I also answered yes to “Enable distributed caching to make your CI faster?”.

This creates a nx.json file in the root.

{
"tasksRunnerOptions": {
"default": {
"runner": "@nrwl/nx-cloud",
"options": {
"cacheableOperations": ["build"],
"accessToken": "******"
}
}
},
"defaultBase": "master"
}

Let’s try using the Nx commands. The below one helps us to run all build commands in our monorepo.

Executing commands with Nx

yarn nx run-many --target=build

Now run the command again.

The built time for the two applications went from to 0.48 seconds to 11.75 seconds How cool is that? 😎⚡️

3. Validation: Typescript | ESLint | Prettier

To ensure that all of our projects are validated uniformly, create a validation script in our root directory.

Prettier

Install prettier. We need to add — ignore-workspace-root or -W to ignore the workspace root check.

yarn add -D prettier -W

Create a .prettierrc file in the root.

{
"singleQuote": true,
"trailingComma": "all",
"semi": false
}

Prettier plugins:

I also added prettier-plugin-organize-imports plugin that helps remove unused imports and sort imports on save. It is a huge time save for me.

yarn add -D prettier-plugin-organize-imports

Automatic plugin discovery happens for prettier < 3. For version above 2, refer to the documentation.

Format scripts

Add scripts in the root package.json file for checking and fixing format issues.

  "scripts": {
"prettier": "prettier \"{apps,libs}/**/*.{ts,tsx,js,json}\" --ignore-path .gitignore",
"format:check": "yarn prettier --check",
"format:write": "yarn prettier --write",
}

Now we can run formatting for the whole monorepo from the root.

yarn format:check
yarn format:write

Type-checking

Add the below tsc commands in the respective files. You will also need to add tsc script to the package.json of the libs you create.

// apps/api/package.json -> "scripts"
"tsc": "tsc"
// apps/web/package.json -> "scripts"
"tsc": "tsc"
// package.json -> "scripts"
"tsc": "nx run-many --target=tsc"

Now, we can enjoy type-checking the whole monorepo from the root.

yarn tsc

Linting

Both our nestjs and nextjs have eslint setup for us with alint script. Add a common lint script with Nx’s run-many feature in the root package.json.

"lint": "nx run-many --target=lint",
yarn lint // Runs linting in both our projects.

Run them all parallel together

In huge projects, each of these commands can take a significant amount of time. Let's create a validate script that runs all three scripts in parallel. You can add other scripts as well liketest.

Install npm-run-all in the root.

yarn add -D npm-run-all -W

Add validate script in the root package.json file.

"validate": "run-p lint tsc format:check",

Now the scripts look like below.

  "scripts": {
"prettier": "prettier \"{apps,libs}/**/*.{ts,tsx,js,json}\" --ignore-path .gitignore",
"format:check": "yarn prettier --check",
"format:write": "yarn prettier --write",
"lint": "yarn nx run-many --target=lint",
"tsc": "yarn nx run-many --target=tsc",
"prevalidate": "yarn format:write",
"validate": "run-p format:check lint tsc"
}

Note I also added a prevalidate script that runs before the validate which runs format:write for us. At this point we can remove the format:check in validate. But I’m going to leave it there.

Run one command, sit back, and enjoy the automation 😎.

yarn validate

It all took 3.5 seconds.

Want to optimize more?

I added lint and tsc to the “cacheableOperations” in nx.json .

{
"tasksRunnerOptions": {
"default": {
"runner": "@nrwl/nx-cloud",
"options": {
"cacheableOperations": ["build", "lint", "tsc"],
"accessToken": "******"
}
}
},
"defaultBase": "master"
}

Our validate script went from 3.5 seconds to 0.74 seconds.

Learn how Nx does the caching in this official documentation.

4. Run validation on the Pre-commit hook.

We have a powerful validation script. Let's run that before every commit by usinghusky and lint-staged.

Initialize husky.

npx husky-init && yarn

This will create a .husky folder with pre-commit hook.

Install lint-staged in the root.

yarn add -D lint-staged -W

Create a lint-staged.config.js in the root.

module.exports = {
'*.{ts,tsx}': (filenames) => ['yarn validate'],
}

Notice that this also runsformat:write before validating due to the prevalidate script.

Modify the pre-commit file in .husky to below.

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

That’s all. Please feel free to play around inducing type, linting, and formatting issues anywhere in the codebase and see if you can commit.

I created a type/lint error in my _app.tsx file.

  const a = 5
a = 'str'

Then, I tried to commit

git add .

git commit -m 'testing pre-commit'

The commit fails as expected. 😎

This drastically improves the quality of commits and stops dirty commits in the developers' local environment.

Fix the type error and your commit will go through.

Fire exit?

What if your building is on fire and you are stuck with a type error that stops you from pushing your code?

Use --no-verify.

git commit -m 'testing no-verify' --no-verify

This will skip the pre-commit validation altogether in emergency situations.

Conclusion

We have done a lot today. We created a mono repo. Set up Nx build system to run cacheable commands. We created a validate script with a pre-commit hook facility. All these setups will help us keep all our projects in one place safe. And safe codebases make happy developers.

Happy coding! 🙌

--

--

Karthick Ragavendran

Fullstack engineer | React, Typescript, Redux, JavaScript, UI, Storybook, CSS, UX, Cypress, CI/CD.