Stop Committing Code That Makes You Say, "Oh My Gosh, What Have I Done?”

Tushar ShuklaTS
Tushar ShuklaA senior frontend developer, curious tech tinkerer and an anime fan.
0 views
6 mins read
A Husky dog and companion Lint on stage, guarding codebase
A Husky dog and companion Lint on stage, guarding codebase
Alright, buckle up, frontend friends! Let's talk about keeping our code clean with Husky and lint-staged. No more committing code that's messier than a Delhi's Tuesday market during Diwali!

The Problem Statement

We've all been there. You're rushing to commit, thinking, "Chalta hai yaar, baad me dekhta hu" (It's OK, I'll check this later). Then, "later" becomes "never," and your codebase looks like Salman Khan's dance scene gone wrong – beautiful, isn't it? (pun intended, slowpokes).
Man dancing with face covered by his t-shirt
Man dancing with face covered by his t-shirt


Or may be you pushed code in a hurry only to realize you left a console.log('debugging forever, YOLO!') in production? Or maybe a teammate (definitely not you) pushed some... let's say questionable code, breaking the build?

Ok, let's take another scenario, imagine that you've been working hard on a feature for days. The sprint is nearing it's end and you're short on time. You commit your changes, push to remote, and create a pull request, expecting it to get approved reviewed ASAP. Then your team lead comments:
PR rejected: Code doesn't follow style guidelines.
Fix the 47 linting errors and resubmit.
Ouch! That's not just embarrassing - it's inefficient. If only there was a way to automatically check your code before committing it...
That's where Husky and lint-staged come in, like your code's personal bouncers, ensuring only the best gets through.

What all are we going to cover?

  • Setting up Husky for Git hooks
  • Configuring lint-staged for efficient linting
  • Configuration examples for projects of different sizes
  • Bonus tips and tricks

Step 1: Installing Husky - Your Git Hook Guardian

First, let's install Husky. Think of it as your code's "darbaan" (doorman) for Git hooks.
npm install husky --save-dev
Or, if you're a yarn aficionado:
yarn add husky --dev

Step 2: Setting up Husky - First Line of Defense

# RECOMMENDED
npx husky init
This creates a .husky/ directory in your project root. In previous version, this was done by npx husky install but that isn't recommended anymore.
The init command creates a pre-commit script in .husky/ and updates the prepare script in package.json. Modifications can be made to suit your workflow.
Husky folder at project root
Husky folder at project root

pre-commit git hook in action
pre-commit git hook in action
For now, let's open the pre-commit file and add some changes: echo 'Hold the door!... Hodor' Now, every time you try to commit, you'll see Hold the door!!...Hodor printed. This is the simplest hook, and it proves Husky is working.
You can now change the echo statement to something meaningful like running all tests and then all of your tests will run before a commit is made.
npm test


In case you have more scenarios to cover, you can add more hooks in the .husky/ directory like:
  • pre-push
  • pre-commit
  • post-merge
  • post-commit
  • post-checkout
And many more! You can checkout all the available options in Husky's official documentation.

Why is this needed? Imagine you want to run your tests before every commit. This is where pre-commit hooks shine. They're your first line of defense against bad code.
Side Note: "Husky" sounds like a fluffy, loyal, wolf-like dog that guards your house, but in this case, it's more of a strict gatekeeper that stops bad code from entering the repo. Pair it with lint-staged and your dog gets an updated laser-guided sniffing system.

Step 3: Integrating lint-staged - Selective Cleaning

The problem with running all tests before every commit? It can be painfully slow. Instead, let's use lint-staged, your code's personal "safai karamchari" (cleaning staff). It only cleans the files you're about to commit, making it super efficient.
npm install lint-staged --save-dev
Or, with Yarn:
yarn add lint-staged --dev
Add a lint-staged configuration to your package.json:
{
  "lint-staged": {
    "*.js": "eslint --cache --fix",
    "*.css": "stylelint --cache --fix"
  }
}
This tells lint-staged to run ESLint on all staged JavaScript files and Stylelint on all staged CSS files. The --cache flag speeds things up, and --fix attempts to automatically fix any linting errors.

Why is this needed? Running linters on your entire codebase every commit is like trying to clean the whole neighborhood when you only need to sweep your front porch. lint-staged keeps it focused and fast.

Step 4: Connecting Husky and lint-staged - Teamwork Makes the Dream Work

Now, let's get Husky and lint-staged to work together. Update your .husky/pre-commit file:
npx lint-staged
This runs lint-staged before every commit. Now, your code will be linted automatically!
Example: If you try to commit a JavaScript file with a missing semicolon, ESLint will automatically fix it before the commit goes through. It's like having a "jugaad" (hack) that actually works!

Configuration examples for Different Project Sizes

Small Projects (Solo Dev or Tiny Team)

For small projects, keep it lightweight:
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": "eslint --fix"
  }
}
Husky dog guarding a small house
Husky dog guarding a small house

Medium Projects (Growing Team, Multiple PRs)

Here, we introduce Prettier and TypeScript checking:
Husky dog guarding a mid-size building
Husky dog guarding a mid-size building
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "tsc --noEmit"
    ]
  }
}

Large Projects (Monorepos, Enterprise)

For large projects, we add style linting and unit tests:
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "tsc --noEmit",
      "jest --bail --findRelatedTests"
    ],
    "*.{css,scss}": "prettier --write",
    "*.{test,spec}.{js,jsx,ts,tsx}": "jest --findRelatedTests"
  }
}
Beast Husky dog guarding a secret place
Beast Husky dog guarding a secret place

Bonus Tips and Tricks

1. Skipping Hooks in Emergency

Sometimes you need to "let the dog off the leash." To bypass Husky temporarily:
bash
Copy
git commit -m "Emergency fix" --no-verify
But use this sparingly - it's like letting your dog dig in the garden "just this once."

2. Custom Messages When Commits Fail

Add some personality to your hook failures:
echo "🐶 Woof! Let me check your code first..."
npx lint-staged || (
  echo "🐺 Grrrr! Your code doesn't meet our standards!"
  false
)

3. Performance for Large Codebases

For massive projects, you might want to adjust the lint-staged config to optimize performance:
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix --cache",
      "prettier --write"
    ]
  }
}

Conclusion

With Husky and lint-staged, you'll be committing much cleaner code than Zomato's pizza delivery (mine is always messed up for some reason). Think of it like an Indian mom checking your plate before letting you leave the dining table — if it's not clean (or in this case, well-formatted and linted), you're not going anywhere.

Remember: Good developers write good code, but exceptional developers automate processes to ensure everyone writes good code. And much like owning a husky in real life - it takes some commitment and care, but the loyalty and reliability are worth every bit of effort!

Now go forth and write clean, well-formatted, and error-free code! 🚀
Found an issue?

If you found a typo, incorrect information or have a feature request, please raise an issue by clicking this button.

And that's a wrap!

Piqued your interest?

Let's connect!

With more than a decade into front-end wizardry, I can turn your ideas into pixel-perfect magic, with a side of witty banter and enough positivity to fuel a small city. Let's build something awesome together!