Programming

Node.js ESLint & Prettier Setup Guide for 2026

Tech Setup6 min read
TS

Tech Setup

Published September 21, 2026 · Editorial policy

Node.js ESLint & Prettier Setup Guide for 2026

Node.js tooling has evolved significantly. If you are still cobbling together .eslintrc.*, .prettierrc.*, .eslintignore, and .prettierignore files while fighting plugin conflicts, you are carrying technical debt from the previous decade.

In 2026, the ecosystem has fully standardized around ESLint's Flat Config system (introduced in v9) and streamlined formatting practices. The days of @typescript-eslint/parser and eslint-config-prettier hacks are behind us. Flat config natively cascades, merges, and simplifies everything.

This guide walks you through setting up a modern, zero-friction Node.js project using ESLint v9+ and Prettier, leveraging native ESM, TypeScript (optional, but assumed as standard for Tier-1 devs), and strict code quality controls.

Prerequisites and Environment

Before running a single command, ensure your local environment matches the baseline expectations for a 2026 production Node.js environment:

  • Node.js: v22.x LTS or v24.x Current (utilizing native ESM support by default via "type": "module" in package.json).
  • Package Manager: pnpm (recommended for speed and strict dependency hoisting) or npm v10+.

Initialize your project workspace if you haven't already:

mkdir node-modern-setup
cd node-modern-setup
pnpm init

Open your newly created package.json and ensure you are opting into native ECMAScript Modules:

{
  "name": "node-modern-setup",
  "version": "1.0.0",
  "type": "module",
  "private": true,
  "engines": {
    "node": ">=22.0.0"
  }
}

Installing ESLint (Flat Config) and Prettier

We will install ESLint and Prettier as development dependencies. Notice that we are skipping legacy shared config packages that require complex inheritance chains.

Run the following command to install the core tools:

pnpm add -D eslint prettier

If your project uses TypeScript (which is standard for Tier-1 backend development in 2026), install the official TypeScript ESLint packages alongside them:

pnpm add -D typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin

Understanding the Flat Config Paradigm

In ESLint 9 and later, configuration shifts from hierarchical cascading JSON/JS files (.eslintrc.js) to a single, explicit JavaScript file named eslint.config.js at the root of your project.

Each object in the exported array is a configuration configuration object that can target specific files using files and ignores patterns. This removes ambiguity about which configuration applies to which file.

Configuring Prettier

Prettier handles code formatting (whitespace, line length, semicolons), while ESLint handles code quality (unused variables, logical bugs, security anti-patterns).

Create a .prettierrc file in your root directory. We prefer a clean, modern configuration that aligns with standard enterprise practices:

{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "endOfLine": "lf"
}

To prevent Prettier from formatting files you don't care about (like build outputs, coverage reports, or lockfiles), create a .prettierignore file:

node_modules
dist
build
coverage
pnpm-lock.yaml
package-lock.json

Configuring ESLint (Flat Config)

Now, let's set up eslint.config.js in the root of your project.

Because we want to avoid formatting rules conflicting between ESLint and Prettier, we use eslint-config-prettier (or simply let Prettier run independently via lint-staged). In 2026, the cleanest architecture is letting ESLint focus purely on AST analysis and letting Prettier handle raw text layout.

Create eslint.config.js:

import js from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import globals from 'globals';

export default [
  // Global ignores to prevent scanning build directories and lockfiles
  {
    ignores: ['dist/**', 'build/**', 'node_modules/**', 'coverage/**'],
  },
  
  // Base ESLint recommended rules for JavaScript
  js.configs.recommended,

  // Custom configuration for all TypeScript and JavaScript source files
  {
    files: ['**/*.{js,mjs,cjs,ts}'],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
      },
      globals: {
        ...globals.node,
        ...globals.es2024,
      },
    },
    plugins: {
      '@typescript-eslint': tseslint,
    },
    rules: {
      // TypeScript specific overrides
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      '@typescript-eslint/no-explicit-any': 'warn',
      
      // General Node.js best practices
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'no-unused-vars': 'off', // Handled by @typescript-eslint/no-unused-vars
      'prefer-const': 'error',
      'eqeqeq': ['error', 'always'],
      'curly': ['error', 'all'],
    },
  },
];

Breaking Down the Flat Config File

  • ignores block: Replaces .eslintignore. Any files matched here are entirely skipped by the linter engine.
  • js.configs.recommended: Pulls in ESLint's default sensible rules for modern JavaScript.
  • languageOptions & globals: Instead of guessing environment variables, we explicitly inject Node.js and ES2024 globals using the standard globals npm package (pnpm add -D globals if not installed).
  • rules: Enforces strict coding standards, such as throwing errors on mutable variables (prefer-const), disallowing loose equality (eqeqeq), and flagging stray console.log statements in production codebases while keeping console.error and console.warn accessible.

Integrating Prettier into ESLint

Historically, developers installed eslint-plugin-prettier to run Prettier as an ESLint rule. Performance benchmarks and maintainer recommendations have shifted away from this approach. Running Prettier as an ESLint rule introduces unnecessary overhead during lint operations.

Instead, the modern standard is a two-step verification process:

  1. ESLint checks code correctness.
  2. Prettier checks and fixes code formatting.

To make this seamless, add formatting check scripts to your package.json:

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier --check .",
    "format:write": "prettier --write ."
  }
}

Automating with Git Hooks (Husky & Lint-Staged)

No Tier-1 development workflow is complete without automated guardrails. You want to ensure that developers cannot commit code that violates formatting or linting rules.

We will use Husky (for managing Git hooks) and lint-staged (to run linters only on staged files, keeping commit speeds blazing fast).

Install the packages:

pnpm add -D husky lint-staged

Initialize Husky in your project:

pnpm exec husky init

This command creates a .husky directory with a default pre-commit hook script. Open .husky/pre-commit and replace its contents with:

npx lint-staged

Next, configure lint-staged inside your package.json. This tells the hook to run ESLint and Prettier checks specifically on files staged for commit:

{
  "lint-staged": {
    "*.{js,mjs,cjs,ts}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,yaml,yml}": [
      "prettier --write"
    ]
  }
}

Now, whenever a developer runs git commit, Husky intercepts the commit, lint-staged formats and lints only the modified files, fixes auto-fixable issues, and halts the commit if unresolvable lint errors exist.

IDE Integration (VS Code)

Even with robust CLI tooling and Git hooks, real-time feedback inside your editor is essential for a smooth developer experience. If you are using VS Code, configure your workspace settings to format and lint on save without conflicts.

Create a .vscode/settings.json file in your project root:

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

Ensure your team has the following extensions installed:

  • ESLint (dbaeumer.vscode-eslint)
  • Prettier - Code formatter (esbenp.prettier-vscode)

Because ESLint v9 uses Flat Config, ensure your VS Code ESLint extension is up to date (v3.0+). It automatically detects eslint.config.js without requiring legacy workspace flags.

Testing Your Setup

Let's verify that everything is working harmoniously. Create a sample TypeScript file at src/index.ts with intentional formatting flaws and an unused variable to test both Prettier and ESLint:

// src/index.ts
const greeting: string = "Hello, TypeScript 2026";

function calculateTax(amount:number):number{
  const unusedVar = 42;
  return amount * 0.2;
}

console.log(greeting, calculateTax(100));

1. Test Prettier Formatting

Run the format check command:

pnpm format

You should see output indicating that src/index.ts contains formatting issues (e.g., spacing around colons). Fix them automatically by running:

pnpm format:write

2. Test ESLint Quality Checks

Run the lint command:

pnpm lint

ESLint will flag the unusedVar variable because our configuration specifies '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }].

Output:

/path/to/node-modern-setup/src/index.ts
  5:9  error  'unusedVar' is assigned a value but never used  @typescript-eslint/no-unused-vars

✖ 1 problem (1 error, 0 warnings)

Refactor src/index.ts to clear the error:

// src/index.ts
const greeting: string = 'Hello, TypeScript 2026';

function calculateTax(amount: number): number {
  return amount * 0.2;
}

// eslint-disable-next-line no-console
console.log(greeting, calculateTax(100));

Run pnpm lint again. The output should be clean with a exit code of 0.

Troubleshooting Common 2026 Gotchas

When migrating legacy codebases or setting up Greenfield projects with modern tooling, you may occasionally run into specific edge cases.

Flat Config Not Detecting Files

If ESLint says it processed 0 files, verify that your eslint.config.js file uses explicit glob patterns inside the files array and that you are running ESLint from the directory containing eslint.config.js.

Rule Conflicts between Prettier and ESLint

If you notice ESLint throwing errors about code style (such as max-len, indentation, or quotes), ensure you haven't accidentally imported conflicting stylistic rules into your eslint.config.js. In modern ESLint setups, stylistic rules should be left entirely to Prettier, leaving ESLint to handle logical code correctness.

Monorepo Support

If your Node.js project is part of a Turborepo or pnpm workspace, place a base eslint.config.js at the root and extend or reference it within package-specific flat configs, utilizing workspaces flags where appropriate. The flat config design natively supports cascading arrays across workspace packages without complex path mapping hacks.