# Improving Code Quality with Pre-Commits in JavaScript Projects (Husky)

*"Code quality is critical to project success, but catching issues early can be a challenge. Pre-commit hooks provide a powerful way to shift quality checks left, saving time and reducing costly fixes. This blog will guide you through setting up pre-commit hooks for your JavaScript and TypeScript projects."*

## TTL;DR: Quick Setup Guide

Use this commands if you don’t like to read the details, otherwise go to next step

```bash
# BASIC CONFIG 
git init # Start git config 
npm init # Start npm config 
mkdir src # Create a src folder 
touch ./src/index.js # Create a file 
echo -e "node_modules\n.pnpm-store" > .gitignore # Create a .gitignore file to exclude unnecessary files like node_modules and .pnpm-store from version control.

# HUSKY
pnpm add --save-dev husky
pnpm exec husky init
echo "pnpm pre-commit" > .husky/pre-commit

# LINT STAGED
pnpm add --save-dev lint-staged # requires further setup
echo "{ \"./src/**/*\" :[\"eslint --fix\"], \"*.{json,js,ts,jsx,tsx,html}\": [\"prettier --write --ignore-unknown\"]}" > .lintstagedrc

# ESLINT
pnpm create @eslint/config@latest # Answer the questions according to you current project needs.
pnpm eslint ./src/* # run eslint to detect errors 

# PRETIER
pnpm add --save-dev --save-exact prettier eslint-config-prettier # Install prettier 
echo "{}" > .prettierrc # Create config file for prettier
echo "build\ncoverage\n" >> .gitignore # Add ignored folers
pnpm exec prettier . --write # Format your JS file 

# COMMIT LINT 
pnpm add --save-dev @commitlint/{cli,config-conventional} # Install commit lint 
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.mjs # Create commit lint config file
echo "pnpm dlx commitlint --edit \$1" > .husky/commit-msg # Add commit message linting to commit-msg hook
```

add this to the package.json

```json
{
  "main": "./src/index.js",
  "type": "module",
  "scripts": {
    "pre-commit": "lint-staged",
    "prepare": "husky",
  }
}
```

eslint.config.js

```javascript
import globals from "globals";
import pluginJs from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";

/** @type {import('eslint').Linter.Config[]} */
export default [
  { languageOptions: { globals: globals.browser } },
  pluginJs.configs.recommended,
  eslintConfigPrettier,
];
```

---

## Initial project config

Set up a basic JavaScript project:

```bash
git init # Start git config 
npm init # Start npm config 
mkdir src # Create a src folder 
touch ./src/index.js # Create a file 
echo -e "node_modules\n.pnpm-store" > .gitignore
```

Update `./src/index.js` with the following content:

```javascript
//This function has an intentional bad structure and error for exercise purposes.
   export function sayHello(number
   ) 
        {
    if(number % 2 = 0){
        return true;
    }
 return false;
                    }
```

### Make your first commit

```bash
git add . 
git commit -m "new code change" # this commit dont follow any standar for exercise purpose
git log --oneline # log you current commits
```

Note that this commit contains errors, which is why tools like Husky and ESLint are essential to prevent such issues from progressing further.

## 🧰 Basic tools configuration

Key Tools to Use

1. **Husky**: Automates Git hooks for running pre-commit checks.
    
2. **Lint-Staged**: Ensures only staged files are linted for efficiency.
    
3. **ESLint**: Catches code quality issues and enforces coding standards.
    
4. **Prettier**: Formats code for consistent indentation and readability.
    
5. **Commit-Lint**: Validates commit messages for clarity and consistency.
    

For this blog we will use **pnpm**, so [install it pnpm](https://pnpm.io/installation).

### 1 . Install Husky ( [link](https://typicode.github.io/husky/get-started.html) )

Install and initialize Husky:

```bash
pnpm add --save-dev husky
pnpm exec husky init
```

This creates a `.husky` folder with a `pre-commit` file. Update it to run lint checks:

```bash
|_.husky
|__ pre-commit
```

change the `./.hunsky/pre-commit` file to execute the lint job

```json
echo "pnpm pre-commit" > .husky/pre-commit
```

Now, add another commit with the following errors for testing:

```javascript
 // Intentional bad structure for testing
export function sayHello(number
   ) 
        {
            const result = false;
    if(number % 2 == 0){
        result = true;
    }
 return result;
                    }
```

Update `package.json` to include pre-commit linting:

```json
{
  "name": "precommit-ts",
  "main": "./src/index.js",
  "type": "module",
  "scripts": {
    "pre-commit": "lint-staged",
    "prepare": "husky",
  },
  "devDependencies": {
    "@eslint/js": "^9.18.0",
    "eslint": "^8.57.1",
    "globals": "^15.14.0",
    "husky": "^9.1.7"
  }
}
```

### 2 . Install [Lint-staged](https://www.npmjs.com/package/lint-staged)

Configure lint-staged to lint only staged files:

```json
pnpm add --save-dev lint-staged # requires further setup
echo "{ \"./src/**/*\" :[\"eslint --fix\"], \"*.{json,js,ts,jsx,tsx,html}\": [\"prettier --write --ignore-unknown\"]}" > .lintstagedrc
```

### 3 . Install Eslint ( [link](https://eslint.org/docs/latest/use/getting-started) )

```bash
pnpm create @eslint/config@latest # Answer the questions according to you current project needs.
pnpm eslint ./src/* # run eslint to detect errors
```

### 4 . Install [Prettier](https://prettier.io/docs/en/install) & [eslint+prettier](https://github.com/prettier/eslint-config-prettier#installation)

This allow you to ident your code

```bash
pnpm add --save-dev --save-exact prettier eslint-config-prettier # Install prettier 
echo "{}" > .prettierrc # Create config file for prettier
echo "build\ncoverage\n" >> .gitignore # Add ignored folers
pnpm exec prettier . --write # Format your JS file
```

Add eslint-config-prettier to your ESLint configuration – either to [eslintrc](https://eslint.org/docs/latest/use/configure/configuration-files) or to [eslint.config.js (flat config)](https://eslint.org/docs/latest/use/configure/configuration-files-new).

```javascript
import globals from "globals";
import pluginJs from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";

/** @type {import('eslint').Linter.Config[]} */
export default [
  { languageOptions: { globals: globals.browser } },
  pluginJs.configs.recommended,
  eslintConfigPrettier,
];
```

### 5 . [Commit lint.](https://commitlint.js.org/)

follow [official docs](https://commitlint.js.org/guides/local-setup.html) for updated instructions

```bash
pnpm add --save-dev @commitlint/{cli,config-conventional} # Install commit lint 
# Create commit lint config file
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.mjs
echo "pnpm dlx commitlint --edit \$1" > .husky/commit-msg # Add commit message linting to commit-msg hook
```

Conventional Commits [enforce a clear, st](https://www.conventionalcommits.org)andardized commit message format that organizes Git history, improves traceability, and simplifies collaboration. By categorizing changes (e.g., `feat`, `fix`), it enables automated versioning, streamlined release note generation, and easier error identification. This ensures consistency, accelerates code reviews, and supports tools like Semantic Versioning. Learn more at [Conventional Commits](https://www.conventionalcommits.org).

## Conclusion

By implementing pre-commit hooks and using tools like Husky, ESLint, Prettier, and Commit-Lint, you ensure cleaner commits, reduce errors, and foster better collaboration.

These steps will help you shift left and maintain high code quality from the start.
