nodemon: Auto-Restart in Development
Restarting your server by hand after every edit kills momentum. nodemon watches your project files and automatically restarts the Node process the moment something changes, so the edit-save-test loop becomes nearly instant. It is a development-only tool — you never ship it to production — but for local work it is one of the highest-leverage utilities in the Node ecosystem. Modern Node also has a built-in --watch flag that covers many of the same cases with zero dependencies.
Installing and running nodemon
nodemon belongs in devDependencies because it is only needed while you are coding. Install it locally rather than globally so every contributor gets the same version:
npm install --save-dev nodemon
Swap your usual node server.js for nodemon server.js. By default it watches the current directory for changes to .js, .mjs, .cjs, .json, and a few other extensions, and restarts when any of them change:
npx nodemon server.js
Output:
[nodemon] 3.1.10
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node server.js`
Listening on http://localhost:3000
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Listening on http://localhost:3000
Typing rs then pressing Enter forces a manual restart without touching a file — useful when a change happens outside the watched tree. Wire nodemon into an npm script so nobody has to remember the flags:
{
"scripts": {
"dev": "nodemon server.js",
"start": "node server.js"
}
}
Now npm run dev gives you auto-restart, while npm start runs plain Node for production.
Configuration with nodemon.json
Passing flags on the command line gets unwieldy fast. Instead, drop a nodemon.json file in the project root and nodemon reads it automatically. This keeps the configuration in version control and consistent across the team:
{
"watch": ["src", "config"],
"ext": "js,json,ts",
"ignore": ["src/**/*.test.js", "dist", "logs"],
"exec": "node --env-file=.env src/server.js",
"delay": 500,
"env": {
"NODE_ENV": "development"
}
}
You can alternatively nest the same keys under a nodemonConfig block in package.json if you prefer a single config file. The most useful options are:
| Option | Type | Purpose |
|---|---|---|
watch | string[] | Directories or files to monitor for changes |
ignore | string[] | Glob patterns to exclude (tests, build output, logs) |
ext | string | Comma-separated extensions that trigger a restart |
exec | string | Command to run, e.g. for TypeScript or custom flags |
delay | number | Milliseconds to debounce before restarting |
env | object | Extra environment variables for the child process |
events | object | Shell commands to run on lifecycle events (start, restart, crash) |
nodemon does not crash when your app does. If your process exits with an error it prints
app crashed - waiting for file changes before starting...and sits idle until you save again. This is intentional — it lets you read the stack trace instead of having the terminal scroll away.
Watch and ignore patterns
Watching too much makes restarts noisy and slow; watching too little means changes get missed. Be explicit. The watch array narrows monitoring to the directories that actually contain source, and ignore removes the rest:
{
"watch": ["src"],
"ignore": ["**/*.test.js", "**/*.spec.js", "public/**", "*.md"]
}
By default nodemon already ignores node_modules, .git, and dotfiles, so you rarely need to list those. Patterns use standard globbing — ** matches any depth, * matches within a path segment. You can express the same intent on the command line for one-off runs:
npx nodemon --watch src --ext js,json --ignore "src/**/*.test.js" src/server.js
For non-JavaScript workflows, point exec at the right runner. To develop a TypeScript server without a separate build step, combine nodemon with tsx:
{
"watch": ["src"],
"ext": "ts",
"exec": "tsx src/server.ts"
}
Node’s built-in --watch flag
Since Node.js 18.11 (stable from 20 onward) the runtime ships its own watch mode, so for many projects you can drop the nodemon dependency entirely. Pass --watch and Node restarts the process when any imported file changes:
node --watch server.js
Output:
Listening on http://localhost:3000
Completed running 'server.js'. Waiting for file changes before restarting...
Restarting 'server.js'
Listening on http://localhost:3000
The built-in watcher is smart: it tracks the actual module graph rather than a directory, so it only restarts on files your app really uses. Scope it with --watch-path and exclude paths via the dedicated flag. It also pairs naturally with the native env-file loader:
node --watch-path=./src --env-file=.env src/server.js
A common all-native dev script needs no third-party tooling at all:
{
"scripts": {
"dev": "node --watch --env-file=.env src/server.js"
}
}
The two approaches differ in scope and flexibility:
| Capability | nodemon | node --watch |
|---|---|---|
| Extra dependency | Yes (dev only) | No |
| Watch by module graph | No (by directory) | Yes |
Custom exec / runners | Yes | No |
| Lifecycle event hooks | Yes | No |
Manual rs restart | Yes | No |
| Config file | nodemon.json | Flags only |
Use
node --watchwhen your project is plain Node and you want zero extra tooling. Reach for nodemon when you need custom run commands (TypeScript, transpilers), debounce control, lifecycle hooks, or fine-grained ignore rules.
Best Practices
- Keep nodemon in
devDependenciesand run production with plainnode— never ship a watcher to a live environment. - Drive it through an npm
devscript so flags and config stay consistent across the team. - Scope
watchto your source directories andignoretest files, build output, and logs to avoid restart storms. - Use
nodemon.json(ornodemonConfiginpackage.json) instead of long command lines so configuration lives in version control. - Set a small
delaywhen an editor or build tool writes many files at once, to debounce noisy restarts. - Prefer Node’s built-in
--watchfor simple JavaScript projects to eliminate a dependency entirely. - Pair watch mode with
--env-fileso your dev process loads configuration the same way every time.