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

> Boost your Node.js projects with ESLint plugins. Learn to use ESLint and Node.js-specific plugins like eslint-plugin-node to prevent runtime vulnerabilities and improve code quality.

- Repository: [Yoni Goldberg/nodebestpractices](https://github.com/goldbergyoni/nodebestpractices)
- Tags: tutorial
- Published: 2026-02-26

---

**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:

```bash
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`](https://github.com/goldbergyoni/nodebestpractices/blob/main/.eslintrc.js) file in your project root that extends the recommended configurations and activates the security-focused rules:

```javascript
// .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`](https://github.com/goldbergyoni/nodebestpractices/blob/main/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`](https://github.com/goldbergyoni/nodebestpractices/blob/main/package.json) so your continuous integration pipeline can enforce these rules:

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

```

According to [`sections/production/productioncode.md`](https://github.com/goldbergyoni/nodebestpractices/blob/main/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:

```javascript
// 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:

```javascript
// 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:

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

```

The rule suggests migrating to the WHATWG URL API:

```javascript
// 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:

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

```

**Fix:** Validate paths against a whitelist:

```javascript
// 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:

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

```

Update your [`.eslintrc.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/.eslintrc.js) to extend Prettier last:

```javascript
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`](https://github.com/goldbergyoni/nodebestpractices/blob/main/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`](https://github.com/goldbergyoni/nodebestpractices/blob/main/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`](https://github.com/goldbergyoni/nodebestpractices/blob/main/.eslintrc.js) to use the TypeScript parser while keeping the Node.js plugins:

```javascript
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`](https://github.com/goldbergyoni/nodebestpractices/blob/main/.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`](https://github.com/goldbergyoni/nodebestpractices/blob/main/sections/production/productioncode.md). This dual-layer approach ensures that Node.js-specific vulnerabilities caught by plugins like `eslint-plugin-security` never reach production.