Linting Best Practices
Quality gates that keep TypeScript Node codebases consistent, strict, and CI-enforced.
How to Use This List
- Adopt when bootstrapping ESLint flat config on a new service.
- Tighten warnings to errors once the codebase is clean.
- Run the same commands locally as CI - no special IDE-only rules.
A - ESLint Configuration
- Use ESLint 9 flat config (
eslint.config.js). Legacy.eslintrcis deprecated path for new repos. - Enable
typescript-eslintrecommended or recommendedTypeChecked. Type-aware rules catch async mistakes. - Commit
ignoresfordist/,node_modules/, coverage. Generated output should never be linted. - Separate overrides for tests and scripts. Relax
no-explicit-anyin tests only. - Disable stylistic ESLint rules when using Prettier.
eslint-config-prettierprevents conflicts.
B - Formatting & Developer UX
- Prettier for format; ESLint for correctness. Do not debate tabs in code review.
- Format on save documented for VS Code/Cursor. Same extension and settings across team.
-
format:checkin CI. Read-only verify on every PR. - lint-staged on pre-commit optional; CI is mandatory.
--no-verifymust not bypass quality. -
.prettierignoreexcludes generated OpenAPI and dist. Avoid meaningless format churn.
C - CI Merge Gates
-
npm run typecheck(tsc --noEmit) on every PR. Types are not optional for TS backends. -
npm run lint -- --max-warnings 0for zero-warning policy. Warnings become debt quickly. - Order: install → typecheck → lint → test → build. Fail fast on cheapest checks.
- Pin Node 24.18.0 in CI to match local engines. Type-aware ESLint uses same TS version.
- Monorepos:
turbo run lint typecheckwith cache. Per-package tasks stay in graph order.
D - Architecture & Hygiene
- Ban
console.loginsrc/viano-console. Use structured logger (Pino) in application code. - Enforce import boundaries with
eslint-plugin-import-xor Nx. Apps do not import sibling apps. -
no-extraneous-dependencieson production code. Phantom hoisted deps hide missing declarations. - Run knip periodically for unused exports and deps. Shrink audit surface in monorepos.
- No
@ts-ignorewithout comment and ticket. Prefer@ts-expect-errorwith explanation.
E - Policy & Ownership
- Document how to add a new ESLint rule (RFC or team chat). Avoid drive-by rule wars in PRs.
- Shared
packages/eslint-configin monorepos. One source of truth for all services. - ESLint version locked in root lockfile.
npm ciensures CI and local parity. - Fix lint in the PR that introduces violations. Do not accumulate "fix later" lint debt.
- Pair lint gates with code review checklist. Automation catches structure; humans catch design.
FAQs
Why zero warnings?
Warnings are ignored in busy weeks and become thousands deep. --max-warnings 0 keeps the bar clear.
Is type-aware ESLint required?
Strongly recommended for async Node code. If too slow, scope type-aware config to src/ only.
Should format run before or after lint in hooks?
Prettier first, then ESLint --fix - matches pre-commit and avoids fighting fixes.
How do we adopt lint on a brownfield repo?
One PR enabling rules at warn, fix or suppress with tickets, then flip to error per directory.
Does NestJS need different ESLint rules?
Add @nestjs/eslint-plugin or recommended patterns for injectable classes and module boundaries.
Can we skip lint for docs-only PRs?
Path filters can skip lint when only *.md changes; default should lint on any TS change.
What about SQL or YAML linters?
Out of scope for ESLint; add dedicated tools but keep TS gates unchanged.
How strict should test overrides be?
Relax any and console; keep no-unused-vars and import hygiene.
Who owns the shared eslint-config package?
Platform or architecture team reviews changes; services consume versioned releases.
How does lint interact with AI-generated code?
Same gates apply. Generated code must pass lint, typecheck, and test before merge.
Related
- Linting Basics - flat config walkthrough
- Prettier Integration - format on save setup
- Typecheck in CI -
tsc --noEmitgate
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.