mrkeyoor.com_
Thu 06 Aug 10:55 UTC
npmCLI & Toolingupdated 06 Aug 2026

nodemon

nodemon is a command line wrapper for node. You type nodemon server.js instead of node server.js, it starts your app, watches the working directory for file changes, and when something changes it kills the process and starts it again. Nothing in your code changes; it is a supervisor, not a library, and your app never knows it is there. The interesting parts are the details around that loop: which extensions count as a change, which directories are ignored, how long it waits before restarting, which signal it sends so you can clean up, and how to make it drive something that is not node at all. Because it just reads the file extension and looks up an executable, nodemon --exec 'python -v' app.py watches Python files and restarts a Python process the same way. It also exposes the same functionality as a Node module and emits events you can hook.

Verdict

nodemon still does its one job well and its ignore and signal handling remain more configurable than anything built in. For a new plain-JavaScript project on current Node, node --watch is one flag and zero dependencies, and for TypeScript, tsx watch replaces both nodemon and ts-node.

API stability5/5The command line and nodemon.json keys have been effectively frozen since version 2, the 3.x line only bumped dependencies and the Node floor, and configs written years ago still run unchanged.
Docs3/5The README covers the common cases well and is honest about traps like ignore patterns needing double asterisks and legacy watch in containers, but the full option list lives in CLI help text, the config section says outright that it needs better documentation, and the module API is a link to a doc file.
Maintenance4/5Pushed 3 August 2026 with only 2 open issues (11 counting PRs) and 3.1.14 released February 2026, though this is effectively one maintainer doing dependency upkeep on a feature-complete tool rather than active development.
Ecosystem4/5Around 13.6M weekly downloads, still the default restart command in a huge number of package.json scripts and tutorials, with gulp-nodemon and grunt-nodemon wrappers; new projects increasingly pick node --watch or tsx instead.

Use it if

  • You are on an older Node version, or a mixed team where some machines are, and you want one restart-on-change command that behaves identically everywhere
  • You need to restart something that is not node: a Python script, a Ruby process, a compiled binary, a shell pipeline, all through --exec and execMap
  • You need precise control over the watch loop: multiple --watch paths, quoted glob ignores, a --delay to batch rapid saves, or --signal SIGHUP so your app can drain connections before dying
  • You want to trigger side effects on restart, such as a desktop notification or a cache clear, through the events config or the module API
  • Your project already has a nodemon.json full of tuned settings and the cost of proving a replacement behaves the same is higher than the cost of keeping it
Skip it if

Setup reality

npm install --save-dev nodemon puts the binary in node_modules/.bin, which is not on your PATH, so you run it through an npm script or npx and not by typing nodemon. Defaults then decide most of your experience: it watches the entire working directory recursively, restarts on .js, .mjs, .cjs, .json, .coffee, and .litcoffee changes, and ignores .git, node_modules, bower_components, .nyc_output, coverage, and .sass-cache. That default extension list is why people think it is broken when editing .ts, .env, or template files, and why a project that writes JSON to disk at runtime restarts itself in a loop. Config lives in nodemon.json in the project or your home directory, or under a nodemonConfig key in package.json, but not both: if a nodemon.json exists or you pass --config, the package.json block is ignored entirely. Ignore patterns are matched against the full absolute path and must use ** rather than a single *, so --ignore '*/test/*' silently matches nothing while --ignore '**/test/**' works, and every glob has to be quoted so your shell does not expand it first. There is a default execMap that maps .ts to ts-node, which is removed automatically if NODE_OPTIONS contains --loader or --import, so TypeScript behaviour changes depending on an environment variable you may not have set yourself. Restarts are delivered as SIGUSR2, so graceful shutdown means listening for that signal with on and not once. Inside containers and on mounted volumes you will likely need legacyWatch, and the throttle before checking for changes is one second by default, adjustable with --delay.

Patterns

Start an app and restart on savebasic-usage

npm install --save-dev nodemon
npx nodemon server.js

# package.json
{
  "scripts": {
    "dev": "nodemon server.js"
  }
}

A local install is not on your PATH, so plain nodemon in a terminal either fails or silently runs a different global version. Omit the filename and nodemon reads main or scripts.start from package.json.

Narrow what triggers a restartwatch-and-ignore

nodemon --watch src --watch config \
        --ignore '**/*.test.js' \
        --ignore '**/fixtures/**' \
        -e js,json,ts \
        src/server.js

Ignore patterns match the full absolute path and need ** rather than a single *, so --ignore '*/test/*' matches nothing at all. Quote every glob or your shell expands it before nodemon sees it. Narrowing --watch is also the cheapest fix for high CPU on a large repo.

Move the flags into nodemon.jsonconfig-file

// nodemon.json
{
  "watch": ["src", "config"],
  "ext": "js,json,ts",
  "ignore": ["**/*.test.js", "**/fixtures/**"],
  "delay": 2500,
  "env": { "NODE_ENV": "development" },
  "exec": "node --enable-source-maps src/server.js"
}

delay in a config file is always milliseconds, while --delay 2.5 on the command line means seconds. If nodemon.json exists, any nodemonConfig block in package.json is ignored completely rather than merged.

Keep the config in package.json insteadpackage-json-config

{
  "nodemonConfig": {
    "ignore": ["**/test/**", "**/docs/**"],
    "delay": 2500
  }
}

Same keys as nodemon.json, one less file. Only read when there is no nodemon.json and no --config flag, which is a common cause of settings that appear to be ignored.

Restart something that is not nodenon-node-scripts

nodemon --exec 'python -u' app.py
nodemon --exec 'go run .' --ext go
nodemon --exec 'php -S localhost:8000' --ext php

// nodemon.json, for extensions it does not know
{
  "execMap": { "pl": "perl", "rs": "cargo run --" }
}

With --exec and a target file, nodemon watches that file's extension instead of .js. There is a built-in execMap for py, rb, and ts, and the ts entry is deleted automatically when NODE_OPTIONS contains --loader or --import, which changes behaviour without you touching any config.

Run TypeScript in developmenttypescript-dev-loop

// nodemon.json
{
  "watch": ["src"],
  "ext": "ts,json",
  "exec": "node --import tsx src/server.ts"
}

# or skip nodemon entirely
npx tsx watch src/server.ts

nodemon does not compile anything; it only restarts a process, so a TypeScript setup is always nodemon plus a loader. tsx watch does both and restarts faster because it does not respawn a fresh Node process for the type-stripping layer.

Clean up before the process is killedgraceful-shutdown

// nodemon sends SIGUSR2 on restart
process.on('SIGUSR2', () => {
  server.close(() => {
    db.end().then(() => process.kill(process.pid, 'SIGTERM'));
  });
});

# or pick a different signal
nodemon --signal SIGHUP server.js

Use on and not once: nodemon can send the signal again if the first shutdown takes too long. Your handler must eventually kill the process itself, otherwise nodemon waits and the restart appears to hang. The signal goes to every process in the tree, including cluster workers.

Make it work in a container or on a mounted drivedocker-and-mounts

nodemon --legacy-watch --polling-interval 1000 src/server.js

// nodemon.json
{ "legacyWatch": true, "watch": ["src"] }

Filesystem events usually do not cross a Docker bind mount or a network share, so nothing restarts and the tool looks broken. Polling fixes it and costs continuous CPU proportional to the number of files watched, which is why you narrow watch paths at the same time.

Run a command when nodemon changes staterestart-events

// nodemon.json
{
  "events": {
    "restart": "echo restarted >> .dev.log",
    "crash": "osascript -e 'display notification \"app crashed\"'"
  }
}

Handy for clearing a build cache or pinging a reload endpoint on each restart. The command runs in a shell, so anything slow here is added to every restart cycle.

Restart by hand without stopping nodemonmanual-restart

# in the terminal running nodemon
rs<Enter>

// change the trigger word, or turn it off
{ "restartable": "go" }
{ "restartable": false }

Useful when a change happens outside the watched tree, such as a database migration or a .env edit. Set restartable to false if your app reads stdin, otherwise nodemon eats the input looking for the trigger word.

Drive nodemon from a Node scriptmodule-api

const nodemon = require('nodemon');

nodemon({ script: 'server.js', ext: 'js json', stdout: false })
  .on('start', () => console.log('up'))
  .on('restart', (files) => console.log('changed', files))
  .on('quit', () => process.exit());

nodemon.on('readable', function () {
  this.stdout.pipe(fs.createWriteStream('out.log'));
});

Set stdout: false or the child output goes straight to the console and the readable event never gives you anything to pipe. The module keeps the process alive, so handle quit yourself or your script will not exit.

Check whether you still need itnode-watch-comparison

# built into Node 18.11 and later
node --watch server.js
node --watch-path=./src --watch-path=./config server.js
node --watch --env-file=.env server.js

The built-in watcher tracks files your app actually imports rather than a directory glob, which means fewer spurious restarts and no config file. What it does not give you is ignore patterns, a restart delay, a custom signal, or restarting non-node processes; those are the reasons left to keep nodemon.

Alternatives

PackageRegistryPick it when
tsxnpmYour app is TypeScript or ESM and you want transpiling and watching from one process instead of nodemon plus ts-node
pm2npmYou need this in production: restart backoff, clustering, log management, and startup on boot rather than a dev-time file watcher
onchangenpmYou want to run an arbitrary command on file changes without the node-process wrapper assumptions nodemon makes
concurrentlynpmThe real problem is running a watcher, a compiler, and a server side by side with combined output and one Ctrl-C