How to Set Up Git Hooks in an Nx Repo

Share on facebook
Share on google
Share on twitter
Share on linkedin

Git hooks can be used to automate tasks in your development workflow. The earlier a bug is discovered, the cheaper it is to fix (and the less impact it has). Therefore it can be helpful to run tasks such as linting, formatting, and tests when you are e.g. committing and pushing your code, so any problems can be caught as early as possible.

This post will cover how to get an efficient development workflow using Git hooks that runs the necessary tasks at the correct times in your development workflow, all automatically.

TLDR:
We will use Husky to execute tasks for these hooks:

commit-msg: Ensuring our commit messages are following conventional commits format.
pre-commit: Run linting and formatting on staged files using lint-staged with eslint, style-lint and nx format:write.
pre-push: Run unit and component tests on affected projects.

Installing Husky

We are using Husky to enable us to run node scripts on Git hooks.

We install and init Husky with:

npx husky-init && npm install

This will also add a prepare script in the package.json:

"prepare": "husky install",

commit-msg

It’s essential to have your team agree on a format for writing commit messages. Especially if you are working on a library and you want to generate a CHANGELOG.md file that shows the latest changes. Commitlint allows you to check the if commit message follows a specific convention (such as conventional commits like those used in the Angular repo).

Setting up commitlint

We first install commitlint as:

npm install --save-dev @commitlint/{config-conventional,cli}

Then we configure it to use conventional commits:

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

We can now hook this into the commit-msg git hooks with Husky by creating a hook inside the .husky folder:

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

We use npx so we don’t need to have dependencies like commitlint installed globally but can use the local package.

This will generate a file looking like this:

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

npx --no -- commitlint --edit "$1"

And now it will give us an error if we try to make a commit with a commit message that doesn’t follow the commit convention:

pre-commit

For pre-commit we want to lint and format the repo, so every developer follows the same coding guidelines and formatting rules automatically. It’s important to run this as a git hook and not just rely on the CI catching this as that would lead to extra commits (eg. fix: lint), slower development due to a slower feedback cycle, and wasted CI agent resources.

With Nx we can use nx lint to lint the files based on our eslint configuration, nx format:write for formatting (uses prettier under the hood) and stylelint to lint scss files and combine it with lint-staged to only run lint on the staged files about to be committed.

Setting up lint-staged

We first need to set up lint-staged:

npm install --save-dev lint-staged

Let’s go ahead and use lint-staged to run lint and formatting before committing.

Running lint and formatting

An Nx project automatically sets up prettier and eslint which are used under the hood correspondingly for the commands nx format and nx lint so we don’t need to cover the specific setup for that here.

We do need to configure stylelint first.

Setting up stylelint

First we set up stylelint with:

npm init stylelint

And we set up a basic stylelint config that supports scss by creating a .stylelintrc.json containing:

{
	"extends": ["stylelint-config-standard", "stylelint-config-prettier"],
	"plugins": ["stylelint-scss"],
	"rules": {
	}
}

You can override the default rules as needed in the rules object.

Setting up the hook

We create a .lintstagedrc to run commands on staged files:

{
    "*.ts": [
	"nx affected:lint --fix --files"
    ],
    "*": [
        "npx nx format:write --files"
    ],
    "*.scss": [
	"npx stylelint --fix"
    ]
}

We make a pre-commit hook that triggers stage-lint:

npx husky add .husky/pre-commit ‘npx lint-staged –relative’

This gives us:

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

npx lint-staged --relative

We use --relative so lint-staged will apply the paths of the staged files as relative paths which we need for nx format:write.

So now we will have eslint, formatting and stylelint running when we commit.

pre-push

Running tests can be a rather lengthy job especially if you both are running unit and component tests (which is what I recommend). So we want to only run this before we push a branch so we still get fast feedback if a test is failing and can fix it with a git amend commit in case.

npx husky add .husky/pre-push 'npx nx affected -t test && npx nx affected -t component-test'

This will both run test and component-test for any affected project on pre-push.

Github repo

Conclusion

In this post, we learned how we can make our development workflow more efficient and streamlined using Husky for hooking our tools into our Git workflow.

This is part of the training for becoming an Angular architect so if you are interested in learning more about this, check out the next cohort of Angular Architect Accelerator.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

How to Set up a CI pipeline with Azure Pipelines and Nx

It goes without saying that having a CI pipeline for your Angular apps is a must. Setting one up for regular Angular apps is fairly straightforward but when you have an Nx monorepo there are certain other challenges that you have to overcome to successfully orchestrate a “build once, deploy many” pipeline. This post will

Read More »

The Stages of an Angular Architecture with Nx

Long gone are the times when the frontend was just a dumb static website. Frontend apps have gotten increasingly complex since the rise of single-page application frameworks like Angular. It comes with the price of increased complexity and the ever-changing frontend landscape requires you to have an architecture that allows you to scale and adapt

Read More »

The Best Way to Use Signals in Angular Apps

Since Angular 16, Angular now has experimental support for signals and there is a lot of confusion in the community about whether this is going to replace RxJS or how it should be used in an app in combination with RxJS. This blog post sheds some light on what I think is the best way

Read More »

High ROI Testing with Cypress Component Testing

Testing is one of the most struggled topics in Angular development and many developers are either giving up testing altogether or applying inefficient testing practices consuming all their precious time while giving few results in return. This blog post will change all this as we will cover how I overcame these struggles the hard way

Read More »

Supabase and Angular: A Powerful Combination for Building Web Applications

Supabase is a cloud-based backend as a service (BaaS) platform that provides developers with a set of tools and services for building scalable and secure web applications.It’s much like Firebase but Supabase provides a PostgreSQL database which solves some of the inconveniences with a NoSQL database such as Firestore.For that reason, Supabase has now become

Read More »