How to Use ESLint and Node.js-Specific ESLint Plugins Effectively

Extend ESLint with Node.js-specific plugins like eslint-plugin-node, eslint-plugin-security, and eslint-plugin-require to catch runtime-specific vulnerabilities that standard JavaScript rules miss.

While ESLint provides excellent coverage for generic JavaScript patterns, the default rule set only addresses "vanilla" language features. When building Node.js applications, you need additional protection against runtime-specific pitfalls—such as insecure dynamic require calls, deprecated core APIs, and unsafe file system operations. The goldbergyoni/nodebestpractices repository specifically recommends extending your ESLint configuration with dedicated Node.js plugins to catch these patterns early in the development lifecycle.

Why Vanilla ESLint Is Not Enough for Node.js

Standard ESLint configurations miss critical Node.js-specific anti-patterns that can lead to security vulnerabilities or runtime failures. According to the nodebestpractices README, "Many faulty Node.js code patterns might escape under the radar. For example, developers might require(variableAsPath) files with a variable given as a path which allows attackers to execute any JS script".

Without Node-specific plugins, your CI pipeline may pass code that contains:

  • Dynamic require statements enabling arbitrary code execution
  • Usage of deprecated Node.js APIs scheduled for removal
  • Non-literal file paths in fs module calls vulnerable to path traversal
  • Test framework globals leaking into production code

Essential Node.js ESLint Plugins

The nodebestpractices repository recommends installing a curated set of plugins that address different aspects of Node.js development:

Plugin Purpose Critical Rules
eslint-plugin-node Enforces correct usage of Node.js core APIs and file system conventions node/no-deprecated-api, node/no-unpublished-require
eslint-plugin-security Detects security vulnerabilities including unsafe regex and eval usage security/detect-non-literal-fs-filename, security/detect-eval-with-expression
eslint-plugin-require Prevents dynamic require calls that could execute arbitrary code require/no-dynamic-require
eslint-plugin-mocha Enforces best practices for Mocha test suites mocha/no-exclusive-tests, mocha/no-global-tests
eslint-plugin-jest Provides Jest-specific rules to prevent disabled or focused tests jest/no-disabled-tests, jest/no-focused-tests

Step-by-Step Configuration Guide

Install Dependencies

Begin by installing ESLint and the Node-specific plugins as dev dependencies:

npm install --save-dev eslint \
  eslint-plugin-node \
  eslint-plugin-security \
  eslint-plugin-require \
  eslint-plugin-mocha \
  eslint-plugin-jest

Create the ESLint Configuration

Create an .eslintrc.js file in your project root that extends the recommended configurations and activates the security-focused rules:

// .eslintrc.js
module.exports = {
  env: {
    node: true,
    es2021: true,
    mocha: true,
    jest: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:node/recommended",
    "plugin:security/recommended",
    "plugin:mocha/recommended",
    "plugin:jest/recommended",
  ],
  plugins: [
    "node",
    "security",
    "require",
    "mocha",
    "jest",
  ],
  rules: {
    // Security hardening
    "security/detect-non-literal-fs-filename": "error",
    "require/no-dynamic-require": "error",
    
    // Node.js best practices
    "node/no-deprecated-api": "warn",
    "node/no-unpublished-require": "error",
    
    // Test hygiene
    "mocha/no-exclusive-tests": "error",
    "jest/no-disabled-tests": "warn",
  },
};

This configuration follows the guidance in sections/codestylepractices/eslint_prettier.md and the main README's section 3.2 on using Node.js ESLint extension plugins.

Add CI Scripts

Add a lint script to your package.json so your continuous integration pipeline can enforce these rules:

{
  "scripts": {
    "lint": "eslint . --ext .js,.ts",
    "lint:fix": "eslint . --ext .js,.ts --fix"
  }
}

According to sections/production/productioncode.md, using CI tools to detect failures before production deployment is a critical production practice.

Practical Examples

Blocking Dynamic Require Attacks

The eslint-plugin-require plugin prevents dangerous dynamic require patterns that could allow arbitrary code execution:

// bad.js - flagged by require/no-dynamic-require
const userPath = "./services/" + req.query.module;
const service = require(userPath); // ❌ ESLint error

Running npm run lint produces:


error  Unexpected dynamic require  require/no-dynamic-require

Fix: Use a static lookup map instead:

// good.js
const services = {
  sms: require("./services/sms"),
  email: require("./services/email"),
};

const service = services[req.query.module]; // Safe lookup

Preventing Deprecated API Usage

The eslint-plugin-node detects usage of deprecated Node.js APIs:

// deprecated-api.js
const { parse } = require("url"); // ❌ node/no-deprecated-api warning

The rule suggests migrating to the WHATWG URL API:

// updated.js
const { URL } = require("url");
const myUrl = new URL("https://example.com/path");

Securing File System Operations

The eslint-plugin-security identifies potentially unsafe file system calls:

// risky-fs.js
const fs = require("fs");
fs.readFileSync(userInput); // ❌ security/detect-non-literal-fs-filename

Fix: Validate paths against a whitelist:

// safe-fs.js
const ALLOWED_PATHS = new Set(["/etc/config.json", "/var/data.json"]);
if (ALLOWED_PATHS.has(userInput)) {
  const data = fs.readFileSync(userInput);
}

Integrating with Prettier

To avoid formatting conflicts between ESLint and Prettier, install the compatibility plugins:

npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier

Update your .eslintrc.js to extend Prettier last:

module.exports = {
  extends: [
    "eslint:recommended",
    "plugin:node/recommended",
    "plugin:security/recommended",
    "plugin:prettier/recommended", // Must be last
  ],
  // ... other config
};

Now npm run lint will enforce both security rules from the Node.js plugins and consistent formatting via Prettier, as documented in sections/codestylepractices/eslint_prettier.md.

Summary

  • Extend beyond vanilla ESLint: The default ESLint rules miss Node.js-specific vulnerabilities like dynamic requires and deprecated APIs.
  • Install targeted plugins: Add eslint-plugin-node, eslint-plugin-security, eslint-plugin-require, and test-specific plugins (mocha, jest) to your dev dependencies.
  • Configure strict rules: Enable require/no-dynamic-require, security/detect-non-literal-fs-filename, and node/no-deprecated-api to catch security issues before production.
  • Automate in CI: Add an npm run lint script to your CI pipeline, as recommended in sections/production/productioncode.md, to prevent faulty code from reaching production.
  • Combine with Prettier: Use eslint-config-prettier and eslint-plugin-prettier to maintain consistent formatting without conflicting with security rules.

Frequently Asked Questions

What is the difference between eslint-plugin-node and eslint-plugin-security?

eslint-plugin-node focuses on correct usage of Node.js core APIs, file system conventions, and package.json metadata. It catches deprecated APIs, unpublished requires, and incorrect shebangs. eslint-plugin-security specifically targets vulnerability patterns like unsafe regular expressions, eval usage, and non-literal file paths that could enable path traversal attacks. Use both together for comprehensive coverage.

Can I use these plugins with TypeScript?

Yes. Install @typescript-eslint/parser and @typescript-eslint/eslint-plugin, then update your .eslintrc.js to use the TypeScript parser while keeping the Node.js plugins:

module.exports = {
  parser: "@typescript-eslint/parser",
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:node/recommended",
    "plugin:security/recommended",
  ],
  // ... other config
};

The Node.js specific rules will analyze the compiled JavaScript patterns while TypeScript rules handle type safety.

How do I handle legacy code with many ESLint errors?

Start by running ESLint with the --fix flag to automatically resolve formatting and fixable issues. For remaining errors, use ESLint's inline disable comments sparingly for critical legacy sections, then gradually refactor. You can also temporarily downgrade specific rules from "error" to "warn" in your .eslintrc.js while your team addresses the technical debt, ensuring new code doesn't introduce additional violations.

Should I run ESLint in pre-commit hooks or only in CI?

Run ESLint in both locations. Pre-commit hooks (using Husky and lint-staged) provide immediate feedback to developers and prevent faulty code from entering the repository. CI pipelines (GitHub Actions, CircleCI, etc.) serve as the final gate to ensure that merged code meets standards, as documented in sections/production/productioncode.md. This dual-layer approach ensures that Node.js-specific vulnerabilities caught by plugins like eslint-plugin-security never reach production.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →