Code alone isn’t enough for production systems.
Writing code is one thing; maintaining it over time is another. In real-world projects, long-term quality depends not just on what you…
Code alone isn’t enough for production systems.
Writing code is one thing; maintaining it over time is another. In real-world projects, long-term quality depends not just on what you write, but how you write it, commit it, and verify it before it ever reaches production.

This is where disciplined workflows come in. A well-maintained codebase usually enforces a few core practices:
- Consistent code formatting
- Clear and standardized commit messages
- Automated checks before code is committed
These checks can include unit tests, build validations, linting rules, and formatting enforcement. The goal is simple: catch problems early, reduce review friction, and keep the codebase predictable as it grows.
In this article, we’ll explore how tools like Husky and Commitlint, combined with ESLint for code quality and Prettier for formatting, work together to enforce standards automatically — right at the developer’s keyboard, before code ever leaves your machine.
Husky
Husky has one simple job: run scripts at specific Git events like pre-commit, commit-msg, and pre-push.
In practice, this means that when you’re about to commit some changes, you can make sure the code follows certain rules before the commit is created. These rules could include running lint checks, verifying formatting, or even executing a small set of unit tests.
Setting up Husky
Installing Husky is straightforward. First, add it as a development dependency:
bun add -d husky
Then initialize Husky:
bunx husky init
This creates a .husky/ directory in your project.
Inside this folder, you can define Git hooks. For example, create a pre-commit file and add the commands you want to run before every commit:
bun run format
Now, whenever you try to commit code, Husky will automatically run these commands. If they fail, the commit won’t go through.
Commitlint
The internet is full of memes about bad commit messages — and most of us have written one. Huge changes, committed as “small changes”.
With AI generating code faster than ever, this problem is only getting worse. Vague commit messages make it harder to understand why a change was made and turn git log into a guessing game.
This is where Commitlint helps.
Commitlint validates commit messages against a set of rules. It doesn’t force long explanations, but it ensures commits follow a consistent structure, often based on conventions like Conventional Commits.
When paired with Husky, Commitlint runs automatically on the commit-msg hook, rejecting invalid commit messages before they ever reach the repository. The result is a cleaner, more readable, and more useful Git history.
Setting up commitlint.
bun add -d @commitlint/cli @commitlint/config-conventional
After installing, create a configuration file that tells Commitlint which rules to use:
// commitlint.config.js
export default { extends: ['@commitlint/config-conventional'] };
Eslint
ESLint was created to help developers follow best practices while writing JavaScript. As TypeScript grew in popularity, support for TypeScript was added through dedicated parsers and plugins, which is why ESLint is now commonly used in TypeScript projects as well.
ESLint helps maintain code quality by analyzing code for potential errors and poor patterns. It catches issues like unused variables, unsafe comparisons, missing dependencies, and inconsistent conventions before they turn into real bugs.
ESLint is rule-driven. Project-specific rules are usually defined in an eslint.config.js (or .eslintrc) file, allowing teams to tailor standards to their codebase.
By enforcing a shared set of rules, ESLint makes code more predictable, easier to review, and simpler to maintain — especially in larger or collaborative projects.
Prettier
Prettier focuses on one thing only: code formatting.
Unlike ESLint, Prettier doesn’t care about code correctness or best practices. Its job is to take your code and format it in a consistent way every single time — handling things like indentation, line length, quotes, trailing commas, and spacing.
This removes an entire class of problems from code reviews. No more debates about tabs vs spaces or where a line should break. Prettier makes those decisions for you and applies them automatically.
By enforcing a single formatting style across the codebase, Prettier keeps the code clean, readable, and consistent — regardless of who wrote it or which editor they use.
Setting up ESlint with Prettier
I use ESLint with Prettier most of the time. For this article i am sharing the config files used in nestjs project.
First, install the required dependencies:
bun add -d eslint typescript-eslint eslint-plugin-prettier prettier
Next, add the ESLint configuration file (eslint.config.mjs) with the TypeScript-aware setup shown above. This enables type-checked linting using your own tsconfig.json and enforces both code quality and formatting rules.
import tseslint from 'typescript-eslint';
import eslintPluginPrettier from 'eslint-plugin-prettier';
export default tseslint.config({
ignores: ['.eslint.config.mjs'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: './tsconfig.json',
sourceType: 'module',
},
globals: {
node: true,
jest: true,
},
},
plugins: {
'@typescript-eslint': tseslint.plugin,
prettier: eslintPluginPrettier,
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/array-type': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/ban-types': 'error',
'@typescript-eslint/naming-convention': [
'error',
{
selector: ['variable', 'function'],
format: ['camelCase', 'UPPER_CASE'],
},
],
'@typescript-eslint/no-confusing-non-null-assertion': 'warn',
'@typescript-eslint/no-confusing-void-expression': 'warn',
'@typescript-eslint/no-for-in-array': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'prettier/prettier': 'error',
},
});
Prettier is configured separately using a .prettierrc file:
{
"singleQuote": true,
"trailingComma": "all"
}
With this setup:
- ESLint handles code quality and TypeScript-specific rules
- Prettier enforces consistent formatting
- Formatting violations are surfaced as ESLint errors
To run linting, use:
bunx eslint .
This keeps formatting and code quality checks consistent across the codebase and ensures the same rules apply for every developer.
Lint Stagged
Running linting and formatting on the entire codebase before every commit can quickly become slow and frustrating, especially as a project grows. This is where lint-staged helps.
Lint-staged runs linters and formatters only on files that are staged for commit. Instead of checking everything, it focuses on the exact files you’re about to commit, keeping pre-commit checks fast and relevant.
Lint-staged is most commonly used together with Husky. Husky triggers the pre-commit hook, and lint-staged decides which files should be checked. If any of the checks fail, the commit is blocked.
This approach strikes a good balance: strict enforcement without slowing down the development workflow.
Setting up lint-staged
First, install lint-staged as a development dependency:
bun add -d lint-staged
Then add this to your package.json file
"lint-staged": {
"*.{ts}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md}": [
"prettier --write"
]
}
After that, add lint-staged to Husky pre-commit hook
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm lint-staged
Relevant packag.json scripts from the NestJS project
"scripts": {
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"prepare": "husky install",
},
Conclusion
Good production systems aren’t built by writing perfect code once — they’re built by making it hard to do the wrong thing repeatedly.
Tools like Husky, Commitlint, ESLint, Prettier, and lint-staged encode good engineering practices directly into the workflow. They catch issues early, reduce manual effort, and keep teams aligned.
More from me:
Building Hanma, the Shadcn for backend coding.
Stop uploading files to the server unnecessarily
Backend development: more than just CRUDs
A walk through arrays in JavaScript
Building Shadcn for the backend, an overview of Hanma
The problems I am facing while building Hanma
Understanding the conversions in JavaScript.
Functions are crazy in JavaScript
메타데이터
- post_id
- 5fe7e485bd96
- slug
- code-alone-isnt-enough-for-production-systems-5fe7e485bd96
- url
- https://medium.com/@itstheanurag/code-alone-isnt-enough-for-production-systems-5fe7e485bd96
- canonical_url
- https://medium.com/@itstheanurag/code-alone-isnt-enough-for-production-systems-5fe7e485bd96
- author_url
- https://medium.com/@itstheanurag
- status
- ok
- fetched_at
- 2026-07-17 02:32:58