Enforcing Code Standards Across a Multi-Language Team
Code quality in a team isn't about personal preference — it's about reducing review friction and preventing entire classes of bugs. This is the linting and formatting setup we use across TypeScript and Python in a shared monorepo, enforced automatically in CI.
Code quality at scale requires automation. This guide covers our complete linting and formatting strategy across a polyglot monorepo: ESLint with Turborepo caching, Prettier for formatting, Python tools (Black, Flake8, Isort), and CI/CD enforcement.
Part 1: Scalable Linting with Turborepo & ESLint
Shared Configuration
Instead of copy-pasting .eslintrc.js files, we publish a local @repo/eslint-config package.
- packages/eslint-config/nest.js: Base rules for all NestJS microservices.
- apps/api/.eslintrc.js: Extends the base config.
module.exports = {
extends: ["@repo/eslint-config/nest.js"]
};
Intelligent Caching
Turborepo knows your dependency graph. If you only change the payment service, turbo lint will only re-lint that service, saving minutes on every build.
{
"pipeline": {
"lint": {
"dependsOn": ["^lint"],
"outputs": []
}
}
}
Parallel Execution
Running npm run lint executes all lint tasks in parallel. Caching ensures that unchanged services hit the cache instantly.
> turbo run lint
api:lint: cache hit, replaying output...
payment:lint: cache hit, replaying output...
client:lint: cache miss, executing...
Part 2: Opinionated Formatting with Prettier
The One Source of Truth
Our format rules are encoded in .prettierrc, respected by IDEs, commit hooks, and CI:
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
}
ESLint Integration
Using eslint-config-prettier is mandatory to disable conflicting rules:
extends: [
'plugin:prettier/recommended',
]
Beyond JavaScript
We use Prettier for everything:
- JSON:
tsconfig.json,package.json - YAML:
docker-compose.yml, GitHub Actions - Markdown:
README.md, Documentation
Part 3: Strict Python Code Quality
Black, Flake8, Isort
Three non-negotiable tools for Python:
- Black: "The Uncompromising Code Formatter". No arguments about spacing.
- Flake8: Catches logic errors and unused imports.
- Isort: Sorts your imports alphabetically and by type.
Centralized Config
[tool.black]
line-length = 88
target-version = ['py311']
[tool.isort]
profile = "black"
line_length = 88
Pre-Commit Integration
We use pre-commit hooks to fix issues before they even reach the repo.
# .pre-commit-config.yaml
- repo: https://github.com/psf/black
rev: 23.3.0
hooks: [id: black]
Part 4: Automating Quality Checks in CI/CD
The Quality Workflow
Local hooks can be bypassed. The CI pipeline is the final arbiter of quality. We run this on every Pull Request.
GitHub Actions Workflow
name: Quality Check
on:
pull_request:
branches: [main]
jobs:
lint-node:
name: Lint Node.js (ESLint + Prettier)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npx turbo run lint
lint-python:
name: Lint Python (Black + Flake8)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- run: pip install black flake8
- run: black --check .
- run: flake8 .
Production Tips
- Branch Protection: Configure GitHub Settings to require the
Lint Node.jsandLint Pythonchecks to pass before merging. This prevents "broken windows" in your codebase. - Husky: Use Husky to run linters on
pre-commitlocally. It saves CI minutes by catching errors before the push. - Rule Depreciation: Review your ESLint rules every 6 months. If a rule is constantly ignored or disabled, remove it. Rules should help, not hinder.
Written by

Technical Lead and Full Stack Engineer leading a 5-engineer team at Fygurs (Paris, Remote) on Azure cloud-native SaaS. Graduate of 1337 Coding School (42 Network / UM6P). Writes about architecture, cloud infrastructure, and engineering leadership.