Beyond Node Scripts: Using Nodemon's --exec for Multi-Language Hot Reload
Nodemon's --exec flag turns it into a language-agnostic restart-on-change wrapper. Here's how to combine it with --ext, --watch, and --ignore for TypeScript, Python, and monorepo workflows — plus the pitfalls to check first.
10 Feb 2026, 20:47 UTC

Your API is TypeScript, your worker is Python, and your schema lives in YAML. Nodemon's default behavior — restart a Node script when a .js file changes — covers none of that. The good news: nodemon was never really about Node. Its --exec flag turns it into a generic "restart this command when these files change" wrapper, and combined with --ext, --watch, and --ignore, it handles multi-language monorepo workflows surprisingly well.
The thesis here is simple: instead of adopting a separate file-watcher per language (ts-node-dev, watchdog, entr), one nodemon config can orchestrate all of them — as long as you understand how the flags interact and where the sharp edges are.
What --exec actually does
Nodemon normally runs your script through node. With --exec, it runs whatever command you give it and restarts that child process when a watched file changes:
# TypeScript via ts-node
nodemon --exec ts-node src/index.ts
# A Python service
nodemon --exec "python3 app.py" --ext pyTwo details matter. First, the positional argument (src/index.ts) is passed to the exec command, not interpreted by nodemon. Second, once you're not running JavaScript, nodemon's default extension list no longer matches your files — which is why --ext py is required in the Python example. Without it, nodemon restarts on .js/.json changes that may not even exist in that directory.
If your command needs shell features like pipes or &&, quote carefully or invoke a shell explicitly: --exec "sh -c 'tsc && node dist/index.js'". Unquoted metacharacters get parsed by your shell before nodemon ever sees them, which produces confusing behavior.
A worked example: TypeScript API plus YAML schemas
Say your service should restart when TypeScript source, JSON config, or YAML schema files change — but never when compiled output changes (that would cause a restart loop):
nodemon --exec ts-node \
--ext ts,json,yaml \
--watch src --watch config \
--ignore 'dist/**' --ignore '**/*.test.ts' \
--signal SIGTERM \
src/index.tsRun this from the package root as your normal user; no elevated permissions needed. Each flag earns its place:
--ext ts,json,yaml— restart on source, config, and schema edits.--watch src --watch config— restrict watching to directories that matter, instead of the whole project tree.--ignore 'dist/**'— if your build emits intodist/, ignoring it prevents the classic loop where a restart triggers a build, the build writes files, and those writes trigger another restart.--signal SIGTERM— nodemon's default kill signal is SIGUSR2, which ts-node and many non-Node runtimes don't handle gracefully. SIGTERM usually gives you a cleaner shutdown. Verify by watching your shutdown logs on restart.
To confirm the setup works, run nodemon --dump first — it prints the merged configuration from CLI flags, nodemon.json, and package.json, so you can see exactly which rules are in effect. Then touch a watched file (touch config/app.yaml) and confirm a restart line appears in the output.
Scaling to a monorepo
For a root-level config that watches multiple packages, a nodemon.json keeps things readable:
{
"watch": ["packages/api/src", "packages/worker"],
"ext": "ts,py,json",
"ignore": ["**/dist/**", "**/__pycache__/**", "**/node_modules/**"],
"delay": "500"
}Then per-package scripts in the root package.json supply the language-specific exec command:
"dev:api": "nodemon --exec ts-node packages/api/src/index.ts",
"dev:worker": "nodemon --exec \"python3 packages/worker/main.py\""The delay value debounces rapid saves (editors with format-on-save can write several files at once), but it adds latency to every restart — tune it per project rather than cargo-culting a number.
One caveat: avoid running several nodemon instances whose --watch paths overlap. Two watchers both reacting to the same file save can trigger restart storms where each restart writes files the other watcher sees. Prefer one orchestrating instance, or give each instance disjoint watch paths.
Limitations worth knowing before you commit
- File events can vanish on network mounts. Docker volumes and WSL2 filesystems frequently miss change events. If restarts stop firing, fall back to polling with
--legacy-watch(orCHOKIDAR_USEPOLLING=1), at the cost of higher CPU. Test by creating a file on the host and confirming the container restarts within a couple of seconds. - Version floor. Nodemon 3.x dropped support for old Node versions (12/14). Check
nodemon --versionand your CI image's Node version before upgrading. - It's still one process at a time. Nodemon restarts a single child; it's not a process supervisor. For running the API and worker together you still need a concurrency tool or a wrapper script using nodemon's programmatic events (
nodemon.on('restart', ...)).
Where to start
Pick the service in your repo with the most annoying manual restart loop, wrap its run command in nodemon --exec with an explicit --ext and --ignore, and verify with nodemon --dump plus a touched file. Once that feels solid, hoist the shared rules into a root nodemon.json. You get one mental model — "restart on change" — across every language in the repo, without adopting a new watcher per stack.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.