NodeJSUseful Developer Tools

Useful Developer Tools

Beyond the linter, formatter, and hooks, a handful of small tools make day-to-day Node development noticeably smoother — running scripts across environments, keeping dependencies healthy and secure, inspecting bundle and install size, and managing Node versions. None is essential on its own, but together they remove friction and catch problems early. This page is a practical tour of the utilities worth knowing: cross-platform script helpers, dependency and security auditing, package analysis, Node version management, and the npm-script conventions that tie your workflow together.

Cross-platform scripts

Bash
npm install --save-dev cross-env rimraf

# cross-env — set env vars the same way on Windows, macOS, and Linux:
#   "build": "cross-env NODE_ENV=production node build.js"
# rimraf — 'rm -rf' that works everywhere (Windows has no rm -rf):
#   "clean": "rimraf dist"
`cross-env` and `rimraf` make npm scripts work on Windows too — don't assume Unix syntax in `package.json`
npm scripts run in the OS's shell, and the **Unix-isms developers reach for break on Windows**: `NODE_ENV=production node app.js` (inline env-var syntax) and `rm -rf dist` simply don't work in the default Windows shell. Two tiny tools fix the common cases: **`cross-env`** sets environment variables in a shell-agnostic way (`cross-env NODE_ENV=production ...`), and **`rimraf`** is a cross-platform `rm -rf` for deleting build output. If your project might be cloned on Windows (or run in mixed CI), prefer these over raw shell syntax in `package.json`. Better still, for anything non-trivial, write the logic in a small Node script (using `fs`, `path`) rather than chaining shell commands — it's portable by default and easier to maintain.
Dependency health and security

Bash
npm outdated          # list dependencies behind their latest version
npm audit             # report known security vulnerabilities in your deps
npm audit fix         # auto-update to patched versions where safe

npx npm-check-updates -u   # bump package.json to latest (review before installing!)
npx depcheck               # find unused dependencies (and missing ones)
Run `npm audit` regularly, but don't blindly `audit fix --force` — forced major upgrades can break your app
Dependencies are the bulk of your code and a major risk surface, so audit them routinely. **`npm outdated`** shows what's behind; **`npm audit`** reports **known security vulnerabilities** (CVEs) in your dependency tree and `npm audit fix` updates to patched versions where it can do so safely within your version ranges. The trap is **`npm audit fix --force`**, which will perform *major-version* upgrades to clear advisories — those are breaking changes that can silently break your app, so never run it unreviewed; upgrade majors deliberately and test. **`npm-check-updates`** helps bump dependencies intentionally (it edits `package.json`, then you install and test), and **`depcheck`** finds dependencies you no longer use (and used-but-undeclared ones). Wire `npm audit` into [CI](/nodejs/ci-cd), and consider automated tools like Dependabot/Renovate to open update PRs continuously rather than letting dependencies rot.
Inspecting package size

Tool

Tells you

Why it matters

npm pack --dry-run

Exact files in your published package

Catch leaked secrets / bloat before publishing

[bundlephobia.com]

A dependency's size & its transitive cost

Decide if a package is worth adding

du -sh node_modules

Total installed size on disk

Spot runaway dependency growth

npm ls <pkg>

Why a package is installed (the dep chain)

Trace where a transitive dep comes from

Know what you're shipping and installing — check published files, dependency weight, and the dependency tree
It pays to *see* the weight of your dependencies and output. **`npm pack --dry-run`** lists exactly what a `npm publish` would upload — the best habit for catching a stray secret or accidentally shipping your whole `src/` (covered in [publishing a CLI](/nodejs/publishing-cli)). For evaluating a *new* dependency, **Bundlephobia** shows its install size and how much it drags in transitively, so you can choose a lighter alternative before committing to it. **`npm ls <pkg>`** explains *why* something is in your tree (which dependency pulled it in) — invaluable when chasing a duplicate or a vulnerable transitive dependency. Being deliberate about size keeps installs fast, deploys small, and your [Docker images](/nodejs/docker) lean.
Managing Node versions

Bash
# nvm (or fnm / volta) lets you install and switch Node versions per project:
nvm install 20
nvm use 20

# Pin the project's version so everyone (and CI) uses the same one:
echo "20" > .nvmrc            # nvm reads this; 'nvm use' picks it up
# package.json: "engines": { "node": ">=20" }  → npm warns on mismatched versions
Use a version manager (nvm/fnm/volta) and pin the project's Node version with `.nvmrc` + `engines`
Different projects often need different Node versions, and the system-installed Node is rarely the right one for all of them. A **version manager** — **nvm**, the faster **fnm**, or **volta** — lets you install multiple Node versions and switch per project or per shell, without sudo or global conflicts. Just as important, **pin the version your project expects**: an **`.nvmrc`** file (containing e.g. `20`) lets `nvm use` select it automatically, and the **`engines`** field in `package.json` makes npm *warn* when someone uses an unsupported version. Pinning means "works on my machine" bugs caused by Node version drift largely disappear, and your local version matches CI and production. Volta goes further by pinning the version *in* `package.json` and switching transparently.
Tie it together with npm scripts

package.json — a conventional script set

JSON
{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "lint": "eslint .",
    "format": "prettier --write .",
    "typecheck": "tsc --noEmit",
    "test": "vitest run",
    "clean": "rimraf dist",
    "check": "npm run lint && npm run typecheck && npm run test"  // one command for CI/local
  }
}
Expose every workflow as a named npm script — discoverable, consistent across machines and CI, and easy to compose
The thread tying all these tools together is **npm scripts**: put every common task behind a clear name in `package.json` so the commands are **discoverable** (`npm run` lists them), **consistent** (the same `npm run build` works on every machine and in CI, regardless of how the underlying tool is invoked), and **composable** (a `check` script that chains lint + typecheck + test gives one entry point for local pre-push checks and CI alike). New contributors don't need to memorize tool flags — they run `npm run dev`, `npm test`, `npm run lint`. This convention is the low-tech glue of a smooth Node workflow: it documents how to work on the project and guarantees humans and automation run the exact same commands.
Next
With a solid workflow in place, it's time to ship: [Deploying Node.js Apps](/nodejs/deployment-intro).