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 v9 introduced “flat config,” a simpler and more powerful configuration system. We recommend this approach.
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: [],
},
];
Install required dependencies
npm install --save-dev eslint eslint-plugin-import globals
Remove legacy configuration
If migrating from older ESLint versions, remove:
.eslintrc.js
.eslintrc.json
.eslintrc.yml
package.json
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?
Add scripts to your package.json
for running ESLint:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
Run checks with:
npm run lint
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.
We take a pragmatic approach to TypeScript adoption, avoiding premature implementation.
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).
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.