engineering-handbook

Code Linting

Maintaining high code quality is essential for building reliable, maintainable Node.js applications. This guide outlines our approach to linting and code quality tools. Our recommended tool for this is ESLint.

ESLint Configuration

Modern ESLint Setup (v9+)

ESLint v9 introduced “flat config,” a simpler and more powerful configuration system. We recommend this approach.

  1. Create a flat config file

    Create eslint.config.js in your project root:

    import js from "@eslint/js";
    import importPlugin from "eslint-plugin-import";
    import globals from "globals";
    
    export default [
      js.configs.recommended,
      importPlugin.flatConfigs.recommended,
      {
        languageOptions: {
          ecmaVersion: "latest",
          globals: {
            ...globals.nodeBuiltin,
          },
        },
      },
      {
        ignores: [],
      },
    ];
    
  2. Install required dependencies

    npm install --save-dev eslint eslint-plugin-import globals
    
  3. Remove legacy configuration

    If migrating from older ESLint versions, remove:

    • .eslintrc.js
    • .eslintrc.json
    • .eslintrc.yml
    • Any ESLint config in package.json

Separating Linting from Formatting

A key principle in our setup is the separation of concerns between linting (code quality) and formatting (code style):

- // Don't use ESLint for formatting
- {
-   "extends": ["prettier"],
-   "plugins": ["prettier"],
-   "rules": {
-     "prettier/prettier": "error"
-   }
- }

Why?

Running ESLint

Add scripts to your package.json for running ESLint:

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

Run checks with:

npm run lint

Customizing Rules

While we generally rely on recommended configurations, you can add project-specific rules if needed:

export default [
  js.configs.recommended,
  importPlugin.flatConfigs.recommended,
  {
    rules: {
      "no-console": ["warn", { allow: ["warn", "error"] }],
      "import/no-unresolved": "error",
    }
  }
];

Always document the reasoning behind custom rules, especially when disabling recommended ones.

TypeScript Considerations

We take a pragmatic approach to TypeScript adoption, avoiding premature implementation.

Using TypeScript Without Compilation

For new projects that need TypeScript, we prefer using Node.js’s built-in TypeScript support rather than a separate compilation step:

# Run TypeScript directly with Node.js (v20+)
node --experimental-strip-type-annotations app.ts

From Node.js v21, you can use the --strip-type-annotations flag (non-experimental).

Integrating with CI/CD

Ensure linting is part of your continuous integration pipeline:

# Example GitHub Actions workflow
name: Lint

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run lint
# Example GitLab Actions workflow
stages:
  - test

default:
  tags:
    - k8s-small

###############################################################################
# Stage: test
lint:
  image: harbor.one.com/nodejs/node:22.14.0-noble
  stage: test
  script:
    - npm ci
    - npm run lint

This ensures all code is linted before being merged, maintaining code quality across the project.