← blog blog / coding-standards-husky-typescript-lint-staged.md

Enforcing coding standards via Typescript, Husky and Lint-staged

This blog walks through setting up Typescript, Husky, and lint-staged for enforcing coding standards in a team. The author shares the configuration process for these tools and provides a workaround for submodules. This approach ensures linting and formatting happens before each commit for better code quality.

Enforcing coding standards via Typescript, Husky and Lint-staged

Typescript is a great tool to enforce coding standards and it helps in removing most of the buggy code beforehand. But it is not enough, we need to have a way to enforce coding standards across the team. We can use husky and lint-staged to enforce coding standards.

ESLint

Just use airbnb’s eslint config. It’s the best. It takes care of most of the things. It’s a great starting point. You can add your own rules on top of it. Mostly you’ll be removing rules because they are very strict.

Configuring ESLint

  1. Install eslint
bun install eslint eslint-config-airbnb-typescript eslint-config-airbnb -D
  1. Add a eslint.cofig.js file to configure ESLint.
module.exports = {
  parser: "@typescript-eslint/parser",
  extends: [
    "airbnb",
    "airbnb-typescript",
    "plugin:@typescript-eslint/recommended",
    "prettier",
  ],
  plugins: ["prettier", "@typescript-eslint"],
};

Add other packages if you want to use them

This config should be rigid enough to enforce coding standards. But if you want to add more rules, you can add them in the config file. Once you do this, you would start seeing lot of errors. Running eslint --fix-all would fix 90% of the issues. So don’t panic.

Husky

Is a tool that helps in running scripts before git commands. We can use it to run scripts before commit, push, etc. We can use it to run tests before commit, run lint before push, etc. This helps to run any npx, bunx commands that we want to, or heck even a .sh file.

Configuring husky

  1. Install husky
bun install husky --save-dev
  1. Add husky to package.json
{
  "devDependencies": {
    "husky": "^8.0.0"
  },
  "husky": {
    "hooks": {
      "pre-commit": "bun run lint-staged"
    }
  },
  "scripts": {
    "lint-staged": "lint-staged",
    "prepare": "husky install"
  }
}

If you are using bun, it’s best to add husky install to postinstall or the prepare script in package.json. This will automatically install husky when you install the package and ensures that everyone in your team has husky installed.

Note: Bun does not print the output of the scripts

This would add a folder .husky to the root of the project. This folder contains the scripts that husky runs before the git commands.

  1. In that folder, there would be a file pre-commit. This file contains the script that husky runs before the commit command. We can add any script that we want to run before the commit command.
# Add this to the end of the file
npx lint-staged

Lint-staged

Lint-staged is a tool that helps in running scripts on staged files. We can use it to run lint on staged files, run tests on staged files, etc. This helps in running scripts on the files that are staged for commit. Also it only runs on staged files, so every commit would have proper changes.

Configuring lint-staged

  1. Install lint-staged
bun install lint-staged --save-dev
  1. Add lint-staged to package.json
{
  "devDependencies": {
    "lint-staged": "^11.0.0"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"]
  }
}

Submodules, Husky, Lint-staged, ESLint

These don’t work well together. If you are using submodules, you would have to install husky and lint-staged in the submodule as well. Also you would have to add the husky hooks to the submodule’s package.json. This is because husky and lint-staged run in the context of the submodule.

If the submodule does not have a package.json it’s fine, you just have to init husky in it. You can add this to the prepare script of your parent app.

husky install && cd path/to/submodule && husky install

ESLint requires a config to be present in your submodule root as well. So just create a config and extend it from your parent app.

module.exports = {
  extends: ["../.eslintrc.js"],
};

You should also add a tsconfig.json to the submodule root. This is because husky and lint-staged run in the context of the submodule. So they would need a tsconfig.json to run the scripts. Just follow the same and extend the config from the parent app.

That’s it. Now every time you commit, husky would run lint-staged and lint-staged would run eslint and prettier on the staged files. This would ensure that the code is linted and formatted before every commit. Cheers.

References

Thanks for reading!

I write about frontend craft, React, TypeScript, and the web. Found this useful? Let me know.

@samuellawrentz →

$ echo "enjoyed this post?" · subscribe via rss ↗

$ git log --oneline --grep="coding practices"

More articles

cd ../blog →
  1. 9fcd71a I Put 'Do Not Overengineer' in CLAUDE.md and Measured What It Does

    Sep 16, 2026 4 min read tag: claude-codetag: ai

    I Put 'Do Not Overengineer' in CLAUDE.md and Measured What It Does
  2. d2e5495 Turn a Book Into Claude Code Skills - What Ten of Them Taught Me About Writing Skills

    Sep 15, 2026 4 min read tag: claude-codetag: ai

    Turn a Book Into Claude Code Skills - What Ten of Them Taught Me About Writing Skills
  3. f43ee7a hmx - Terminal Mind Maps in Plain Text, and Why I Ported h-m-m to Go

    Sep 15, 2026 4 min read tag: terminaltag: go

    hmx - Terminal Mind Maps in Plain Text, and Why I Ported h-m-m to Go
  4. 18674b4 Rules First, Model Second - I Ran Alibaba's open-code-review on My Own Commits

    Sep 13, 2026 4 min read tag: aitag: code-review

    Rules First, Model Second - I Ran Alibaba's open-code-review on My Own Commits

$ giscus --load ./comments

00:00

This helps me increase the session time of my site. Thank you!

Can you stay a bit longer?