ESLint + Prettier
Overviewβ
The application uses ESLint for code quality and consistency, and Prettier for automatic code formatting. Together, they enforce uniform code style across the monorepo.
What Are ESLint and Prettier?β
ESLintβ
ESLint is a static code analysis tool that:
- Detects Problems: Finds bugs, security issues, and problematic patterns
- Enforces Style: Ensures consistent coding style
- Customizable: Highly configurable with plugins and rules
- Automatic Fixes: Can automatically fix many issues
Prettierβ
Prettier is an opinionated code formatter that:
- Consistent Formatting: Auto-formats code consistently
- Zero Configuration: Works with sensible defaults
- Opinionated: Makes style choices so you don't have to
- Language Agnostic: Supports JS, TS, JSX, TSX, JSON, CSS, YAML, etc.
App Configurationβ
Root ESLint Configurationβ
eslint.config.mjsβ
import js from '@eslint/js';
import service from '@typescript-eslint/eslint-plugin';
import parser from '@typescript-eslint/parser';
import eslintConfigPrettier from 'eslint-config-prettier';
export default [
// Ignore node_modules and build directories
{
ignores: ['node_modules', 'dist', 'build', 'coverage', '.next'],
},
// Base JavaScript rules
{
files: ['**/*.js', '**/*.mjs'],
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
rules: {
...js.configs.recommended.rules,
},
},
// TypeScript rules
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser,
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
},
plugins: {
'@typescript-eslint': service,
},
rules: {
...service.configs.recommended.rules,
'@typescript-eslint/explicit-function-return-types': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
},
// Disable Prettier conflicting rules
eslintConfigPrettier,
];
Prettier Configurationβ
.prettierrc.jsonβ
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "always",
"proseWrap": "preserve",
"endOfLine": "lf"
}
.prettierignoreβ
node_modules
dist
build
coverage
.next
pnpm-lock.yaml
out
.turbo
Backend (NestJS) ESLintβ
apps/api/.eslintrc.jsβ
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js', 'dist', 'coverage'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-types': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-restricted-syntax': [
'error',
{
selector:
'CallExpression[callee.object.name="console"][callee.property.name!=/^(warn|error)$/]',
message: 'Unexpected console method',
},
],
},
};
Frontend (Next.js) ESLintβ
apps/web/.eslintrc.jsonβ
{
"extends": ["next", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "react", "react-hooks"],
"rules": {
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/explicit-function-return-types": "warn",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@next/next/no-html-link-for-pages": "off"
}
}
Running ESLint & Prettierβ
ESLint Commandsβ
# Lint all files
pnpm lint
# Lint specific package
pnpm --filter @qms/api lint
# Lint specific file
pnpm lint src/services/user.service.ts
# Fix auto-fixable issues
pnpm lint -- --fix
# Show only warnings
pnpm lint -- --fix --max-warnings=0
# Generate report
pnpm lint -- --format json > lint-report.json
Prettier Commandsβ
# Format all files
pnpm format
# Format specific package
pnpm --filter @qms/web format
# Check formatting without modifying
pnpm format -- --check
# Format specific file
pnpm format src/components/Header.tsx
# Format specific file types
pnpm format -- --write "**/*.{ts,tsx,json,md}"
Combined Lint & Formatβ
# Run both
pnpm lint && pnpm format
# Or in script
pnpm -r run lint:fix && pnpm -r run format
Rules Configurationβ
Common ESLint Rulesβ
{
"rules": {
// Error prevention
"no-console": ["warn", { "allow": ["warn", "error"] }],
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
// Best practices
"eqeqeq": ["error", "always"],
"no-implied-eval": "error",
"no-new-func": "error",
"prefer-const": "error",
"prefer-template": "warn",
// TypeScript specific
"@typescript-eslint/explicit-function-return-types": "warn",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/no-explicit-any": "warn",
}
}
React Specific Rulesβ
{
"plugins": ["react", "react-hooks"],
"rules": {
"react/prop-types": "off", // Using TypeScript
"react/react-in-jsx-scope": "off", // Not needed in JSX transform
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/no-array-index-key": "warn",
"react/no-unescaped-entities": "warn",
}
}
Integrationβ
IDE Integrationβ
VS Code Settingsβ
Create .vscode/settings.json:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
},
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"]
}
Git Hooks Integrationβ
Part of Husky + lint-staged setup (see related documentation):
# Run before commit
pnpm lint --fix
pnpm format
# Prevents bad code from being committed
Common Issues & Solutionsβ
ESLint vs Prettier Conflictsβ
Prettier conflicts are handled with eslint-config-prettier:
// This disables all ESLint rules that conflict with Prettier
import eslintConfigPrettier from 'eslint-config-prettier';
export default [
// ... other configs
eslintConfigPrettier, // Must be last
];
Ignoring Filesβ
Create .eslintignore:
node_modules
dist
build
coverage
.next
pnpm-lock.yaml
Or in config:
{
ignores: ['node_modules', 'dist', '**/*.min.js'];
}
Fixing Formatting Issuesβ
# Fix ESLint issues
pnpm lint -- --fix
# Format with Prettier
pnpm format
# Both together
pnpm lint -- --fix && pnpm format
Best Practicesβ
1. Use Meaningful Rule Configurationβ
// β
GOOD: Tailored to project
{
"@typescript-eslint/explicit-function-return-types": "warn",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
// β AVOID: Too strict everywhere
{
"@typescript-eslint/explicit-function-return-types": "error"
}
2. Disable Rules Judiciouslyβ
// β
GOOD: Explain why rule is disabled
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// Using any because external library types are incomplete
const externalData: any = await externalLib.getData();
// β AVOID: Disable without explanation
// eslint-disable-next-line
const externalData: any = await externalLib.getData();
3. Consistent Prettier Configβ
Use .prettierrc.json for single source of truth:
{
"semi": true,
"singleQuote": true,
"printWidth": 100
}
4. Run Checks Before Commitβ
This is handled by Husky + lint-staged:
# Automatically run on commit
git commit -m "feat: add feature"
# β eslint --fix
# β prettier --write
# β commit if passes
Monorepo Considerationsβ
Root Configurationβ
Configure at root for consistency:
# Lint all packages
pnpm -r run lint
# Format all packages
pnpm -r run format
Package-Specific Overridesβ
Each package can extend root config:
// apps/api/.eslintrc.js
module.exports = {
extends: '../../.eslintrc.js',
rules: {
'no-console': 'off', // Servers can log
},
};
Scripts in Root package.jsonβ
{
"scripts": {
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"code:fix": "pnpm lint:fix && pnpm format"
}
}
Troubleshootingβ
ESLint Not Finding Configβ
# Check if config file exists
ls -la .eslintrc* eslint.config.*
# Verify config syntax
cat .eslintrc.json # Should be valid JSON
Prettier Formatting Conflictsβ
# Ensure eslint-config-prettier is installed
pnpm list eslint-config-prettier
# Place it last in extends
module.exports = {
extends: [..., 'prettier'] // Must be last
}
Performance Issuesβ
# Run with cache
pnpm lint -- --cache
# Check specific file
pnpm lint src/services/user.service.ts
# Profile ESLint
pnpm lint -- --debug