Using nodemon --exec to Run Linting and Tests Before Each Restart
Learn how nodemon's --exec flag lets you run linting and tests on every file change, so the app only restarts when validation passes.
03 Jul 2026, 14:26 UTC

The problem: slow feedback loop
During development you want to know instantly whether a change breaks linting rules or unit tests. The default nodemon behavior restarts the Node process on every file change, but it does not run any validation first. You end up seeing runtime errors only after the app has already started, which adds friction to the edit‑run‑debug cycle.
How --exec changes the loop
The --exec flag tells nodemon to replace the default node invocation with an arbitrary shell command. Nodemon still watches the filesystem, but on each matched change it spawns your command instead of (or before) the application. If the command exits with a non‑zero status, nodemon treats it as a failure and does not restart the app, giving you immediate feedback.
- You can chain multiple tasks (lint → test → start) in a single command string.
- The changed file path is available via the
$NODEMON_FILEenvironment variable or as a positional argument, allowing targeted runs. - Combining
--execwith--watchlets you limit the directories that trigger the command while still executing it on any matched change.
Worked example: lint then test before restart
Assume a typical project layout:
my-app/
├─ src/
│ └─ index.js
├─ test/
│ └─ index.test.js
├─ package.json
└─ nodemon.json
Add a nodemon.json file in the project root with the following content:
{
"watch": ["src", "test"],
"exec": "eslint src && jest --passWithNoTests"
}
Install the required dev dependencies if they are not already present:
npm install --save-dev nodemon eslint jest
Run the development loop from the project root (no elevated permissions needed):
npx nodemon
What happens on each file save:
- Nodemon detects a change in
src/ortest/. - It spawns a shell and runs
eslint src. - If ESLint exits with code
0, the shell continues tojest --passWithNoTests. - Only when both commands succeed does nodemon start (or restart) the actual application – you can add a final
&& node src/index.jsto theexecstring if you want the app to launch automatically. - If either step fails, nodemon prints the error output and keeps the previous process alive (or stays idle if there was none).
You can verify the behavior by intentionally introducing a lint error (e.g., a missing semicolon) and saving the file; the console will show the ESLint report and the app will not restart.
Trade‑offs to watch
- Restart latency: Heavy commands (full test suite, type‑checking) add seconds to each cycle. Keep the
execpipeline fast – run only the most relevant checks (e.g., lint + unit tests for changed files). - Environment inheritance: Nodemon spawns a fresh shell for each run. Variables set in your shell profile are not automatically passed. Explicitly export needed variables in the
execstring (e.g.,NODE_ENV=development eslint src && jest) or use a wrapper script that sources.envfiles. - Failure masking: Because a non‑zero exit stops the restart, a flaky test can block the whole loop. Consider using
|| truefor non‑critical steps, but be aware you lose the guardrail.
Next steps
Add the exec configuration to your existing nodemon setup, then measure the time between a file save and the first line of your app’s log output. If the latency exceeds a comfortable threshold (≈2 s for many teams), split the pipeline: run lint on every change, but defer the full test suite to a pre‑commit hook or CI job. This keeps the inner loop fast while still catching regressions before they land.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.