Mid-level 6 min · March 28, 2026

Nodemon Docker Mac: legacyWatch Fix for Stale Code

On Docker for Mac, Nodemon silently ignores file saves.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Nodemon watches file changes and restarts Node.js automatically in development
  • Install locally with --save-dev; never globally or in production
  • Use nodemon.json to control watch paths, ignore patterns, and debounce delay
  • The infinite restart loop happens when the app writes to watched dirs — fix with ignore
  • On Docker for Mac, file events may not propagate; enable legacyWatch or polling mode
  • For TypeScript, use exec key with tsx — 10x faster than ts-node for large codebases
Plain-English First

Imagine you're editing a Google Doc and every time you change a word, someone has to manually walk over, close the document, and reopen it before you can see the update. That's exactly what developing a Node.js app without Nodemon feels like — you change one line, then you Ctrl+C the server, retype 'node server.js', and wait. Nodemon is the person standing over your shoulder who sees you saved the file and restarts the server automatically before you've even lifted your hands off the keyboard. You write code, flip to the browser, and your change is already live.

I watched a junior dev spend forty-five minutes debugging what turned out to be a change he'd made that was never actually running — because he'd forgotten to restart his server after saving the file. Not once. Three times in the same afternoon. That's not a character flaw; it's a workflow problem, and Nodemon exists to eliminate it entirely.

Node.js doesn't watch your files. When you start a Node.js server with 'node app.js', it loads your code once into memory and runs it. The moment you change a file and save it, Node.js has absolutely no idea. It's still running the old code. The only way to pick up your changes is to kill the process and restart it manually. In a fast feedback loop where you're editing, checking, editing, checking — that's a tax you pay hundreds of times a day. It sounds minor until you've done it for three hours straight and your muscle memory is shot and you've introduced a bug because you were staring at stale output.

After reading this, you'll be able to install Nodemon, wire it into any Node.js project, configure it to watch exactly the files you care about, ignore the ones you don't, and set it up so that every developer who clones your repo gets the same workflow automatically. You'll also know the three configuration mistakes that cause Nodemon to restart infinitely or miss changes entirely — both of which I've seen derail entire dev sessions.

Why Manual Restarts Kill Your Flow (And What Node.js Actually Does)

Before you can appreciate what Nodemon does, you need to understand why Node.js doesn't auto-restart by itself — and it's not an oversight, it's a deliberate design decision.

When you run 'node server.js', Node reads that file, compiles it to bytecode, loads it into memory, and starts executing. From that point on, Node is a running process. It owns a port, it holds database connections, it has state in memory. It doesn't scan your file system for changes because that's not its job. Its job is to serve requests as fast as possible. Polling the disk constantly would waste CPU cycles and slow everything down. So Node.js makes a deal with you: 'I'll be blazing fast, but you handle restarts.'

In development, that deal is terrible. You're changing files every two minutes. Every change requires a Ctrl+C and 'node server.js' again. Worse, you'll forget. You'll save a file, test your endpoint, get the old behaviour, spend ten minutes convinced your logic is wrong, and then finally notice you never restarted. I've seen this exact scenario cause someone to revert a perfectly correct fix because they thought it 'didn't work.'

Nodemon solves this by wrapping the Node.js process. It watches your project files for changes, and when it detects one, it kills the current Node.js process and starts a new one automatically. Your server is always running the latest version of your code. You save, you flip to Postman or your browser, your change is there.

BasicExpressServer.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// io.thecodeforge — JavaScript tutorial

// A minimal Express server — the kind you'd have at the very start
// of building a REST API for, say, a product catalogue service.
// Without Nodemon, every change to this file means manually restarting.

const express = require('express');

const app = express();
const PORT = 3000;

// Parse incoming JSON request bodies (needed for POST/PUT endpoints)
app.use(express.json());

// A simple health-check route — the first thing any real API should have.
// Load balancers and monitoring tools ping this to know the service is alive.
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'ok',
    timestamp: new Date().toISOString()
  });
});

// A placeholder products route — imagine this hitting a database next.
app.get('/products', (req, res) => {
  res.status(200).json({
    products: [
      { id: 1, name: 'Wireless Keyboard', price: 79.99 },
      { id: 2, name: 'USB-C Hub',        price: 49.99 }
    ]
  });
});

// Start the server and bind it to the port.
// Without Nodemon: change ANYTHING above, and this output is lying to you
// until you manually kill and restart the process.
app.listen(PORT, () => {
  console.log(`Product API running on http://localhost:${PORT}`);
  console.log('Tip: You are running the code that was loaded at START TIME.');
  console.log('Changes you make now will NOT appear until you restart.');
});
Output
Product API running on http://localhost:3000
Tip: You are running the code that was loaded at START TIME.
Changes you make now will NOT appear until you restart.
The Invisible Bug Trap:
The most common beginner mistake isn't a syntax error — it's testing against a running server that's still executing code from three saves ago. If you ever think 'this should be working but it's not,' the very first question is: did you restart? Nodemon makes this question irrelevant.
Production Insight
In production, you never want auto-restarts on file change. Node.js code is deployed once, runs until the next deployment. Nodemon in production would restart on log rotation or config file changes — dropping active connections.
Rule: 'start' script uses plain 'node', 'dev' script uses 'nodemon'. Never mix them.
Key Takeaway
Node.js doesn't watch files by design. Nodemon wraps the process to restart on changes.
Manual restarts are error-prone and kill flow.
Always separate dev and production start scripts.

Installing Nodemon and Running Your First Auto-Restarting Server

Here's exactly how to get Nodemon running from nothing. Two decisions upfront: global install vs. local install. I'll tell you the right answer and explain why the other one causes problems on a team.

Global install means you run 'npm install -g nodemon' and the 'nodemon' command becomes available everywhere on your machine. Sounds convenient. The problem is that your teammates might have a different version, or a CI/CD pipeline won't have it at all. Six months later someone clones your repo, runs the start script, gets 'nodemon: command not found', and wastes half an hour figuring out why. Don't do global installs for project tooling.

Local install — 'npm install --save-dev nodemon' — puts Nodemon in your project's node_modules folder and records it in package.json under devDependencies. Anyone who clones the repo and runs 'npm install' gets the exact same version of Nodemon automatically. This is the only approach that works on a team or in CI. The '--save-dev' flag is critical: Nodemon is a development tool, not something that should ever run in production. Separating dev from production dependencies keeps your production Docker image lean and your deployment safe.

Once installed locally, you can't just type 'nodemon server.js' in the terminal — your shell doesn't know where to find it. You access it through npm scripts in package.json, which automatically add your local node_modules/.bin to the PATH when running scripts.

PackageJsonSetup.jsonJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// io.thecodeforge — JavaScript tutorial

// This is your package.json — the configuration file at the root
// of every Node.js project. Think of it as your project's ID card
// and instruction manual combined.

// After running: npm install --save-dev nodemon
// Your package.json should look something like this:

{
  "name": "product-catalogue-api",
  "version": "1.0.0",
  "description": "REST API for product catalogue management",

  "scripts": {
    // 'start' is the production command — plain node, no Nodemon.
    // Never run Nodemon in production. It restarts on file changes,
    // which is the last thing you want on a live server.
    "start": "node server.js",

    // 'dev' is what every developer runs locally.
    // npm run dev => npm finds nodemon in node_modules/.bin automatically.
    // The --rs flag lets you manually type 'rs' + Enter in the terminal
    // to force a restart without changing any file (handy for env var changes).
    "dev": "nodemon server.js"
  },

  "dependencies": {
    // express belongs here — it runs in production
    "express": "^4.18.2"
  },

  "devDependencies": {
    // nodemon belongs HERE, not in dependencies.
    // --save-dev puts it here automatically.
    // This means 'npm install --production' skips it entirely.
    "nodemon": "^3.0.1"
  }
}
Output
// After running: npm run dev
// You will see output similar to this in your terminal:
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
// Now edit ANY .js or .json file and save. You will immediately see:
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
Senior Shortcut:
Always have a separate 'start' and 'dev' script in package.json. 'start' runs plain node for production, 'dev' runs nodemon for local development. If someone accidentally runs 'npm run dev' on a production server, Nodemon watching for file changes on a live machine is a security and stability nightmare — plus, any deployment tool that touches the filesystem would trigger constant restarts.
Production Insight
Global installs break team consistency and CI. Always install locally with --save-dev.
If someone gets 'nodemon: command not found' after cloning, they're missing the local install.
Add a 'dev' script in package.json so npm run dev works out of the box.
Key Takeaway
npm install --save-dev nodemon. Never --global.
Add 'dev' script to package.json.
Separate dev and production dependencies.

Configuring Nodemon: Watch the Right Files, Ignore the Rest

Out of the box, Nodemon watches all .js, .mjs, .cjs, and .json files in your project directory and restarts on any change. For a tiny project that's fine. For anything real, it'll drive you insane.

Here's the real problem: Nodemon doesn't know the difference between your source code and generated files, log files, or temporary files. If your app writes to a log file on every request — which is completely normal — and that log file is inside your project directory, Nodemon sees the write, thinks your code changed, and restarts. Which triggers another request. Which writes to the log. Which triggers another restart. I've seen this infinite restart loop on three separate teams. It eats CPU, it makes your logs unreadable, and it looks like the world's most confusing bug because the server is constantly restarting for no visible reason.

The fix is a nodemon.json configuration file. It sits at the root of your project alongside package.json and gives you precise control over what Nodemon watches, what it ignores, and what file extensions trigger a restart. Every serious Node.js project should have one. It makes the development experience consistent for every person on the team — nobody's getting phantom restarts because of local tooling differences.

nodemon.jsonJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// io.thecodeforge — JavaScript tutorial

// nodemon.json — place this at your project root.
// Nodemon reads this automatically when it starts. No flags needed.

{
  // 'watch' tells Nodemon exactly which directories to monitor.
  // Only changes inside these folders will trigger a restart.
  // Here we're only watching our source code folder, not the whole project.
  "watch": ["src", "config"],

  // 'ext' lists the file extensions that should trigger a restart.
  // We add 'env' because environment config changes need a restart.
  // We DON'T add 'log', 'tmp', or 'json' from generated folders.
  "ext": "js,json,env",

  // 'ignore' is your safety net — anything Nodemon should never restart for.
  // This is where you stop the infinite restart loop before it starts.
  "ignore": [
    "src/**/*.test.js",   // Don't restart when test files change — tests run separately
    "src/**/*.spec.js",   // Same for spec files
    "logs/*",             // Log files change on every request — NEVER watch these
    "node_modules/*",     // npm install changes these — never restart for this
    "coverage/*",         // Test coverage reports are generated, not source code
    "dist/*",             // Built/compiled output — not source code
    ".git/*"              // Git internals — definitely not source code
  ],

  // 'delay' adds a debounce in milliseconds before Nodemon restarts.
  // Without this, saving multiple files quickly (e.g. a refactor touching 5 files)
  // triggers 5 rapid restarts in a row instead of one clean restart.
  // 500ms is the sweet spot — long enough to batch saves, short enough to feel instant.
  "delay": 500,

  // 'env' injects environment variables specifically for the dev session.
  // This means you don't need a separate .env loader for local development basics.
  // For production, these come from your actual environment — NOT from here.
  "env": {
    "NODE_ENV": "development",
    "PORT": "3000"
  },

  // 'verbose' set to true prints exactly which file changed and why Nodemon restarted.
  // Turn this on when debugging a phantom restart — it'll name the culprit file immediately.
  "verbose": false
}
Output
// With this nodemon.json in place, running 'npm run dev' produces:
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/* config/**/*
[nodemon] watching extensions: js,json,env
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
// Edit src/routes/products.js and save — you see:
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
// Write to logs/app.log — Nodemon stays silent. No restart. Perfect.
The Infinite Restart Loop:
If your app writes to any file inside a watched directory — logs, generated files, cache files — Nodemon will restart every time that file is written, which causes the app to start again, write to the file again, and restart again forever. Symptom: '[nodemon] restarting due to changes...' appearing continuously with no file saves on your end. Fix: add the offending directory to the 'ignore' array in nodemon.json. Run with 'verbose: true' first to see exactly which file is causing it.
Production Insight
The infinite restart loop is the #1 production incident in dev environments. It's not a bug — it's a config gap.
Always add 'logs/', 'dist/', 'node_modules/', '.git/' to ignore before you hit this.
Use 'delay': 500 to batch rapid saves and reduce unnecessary restarts.
Key Takeaway
Use nodemon.json for all config.
Ignore directories that the app writes to.
Set delay to 500ms to debounce multi-file saves.

Nodemon With ES Modules, TypeScript, and Multi-Process Apps

Basic Nodemon for a CommonJS Express app is straightforward. But modern Node.js projects use ES Modules ('import'/'export' instead of 'require'), TypeScript, or even multiple processes. Each one needs a small adjustment or you'll hit a wall and not know why.

ES Modules in Node.js require either a .mjs extension or '"type": "module"' in your package.json. Nodemon itself handles this fine — the restart mechanism doesn't care about your module system. But the command you give Nodemon matters. If you're using '"type": "module"' and running Node 18+, plain 'nodemon server.js' still works. No change needed there.

TypeScript is the one that trips people up. Nodemon can't execute TypeScript directly — Node.js can't either. You need a TypeScript executor. The most common choice for development is 'ts-node'. You tell Nodemon to use ts-node as the executor instead of node, and it handles compilation on the fly. For larger projects, 'tsx' has become the faster alternative — it's built on esbuild and noticeably quicker for big codebases. I've seen teams on large TypeScript monorepos where ts-node's restart time was 8-12 seconds per change. Switching to tsx dropped that to under 2 seconds. That compounds over a full work day into real productivity.

For any of these setups, the exec key in nodemon.json is your control lever. It replaces 'node' with whatever executor you need.

nodemon.typescript.jsonJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// io.thecodeforge — JavaScript tutorial

// nodemon.json configured for a TypeScript Node.js project.
// This is the setup you'd use for a TypeScript Express API —
// the kind of thing you'd find in a fintech or SaaS backend.

{
  // 'watch' stays the same — source code directories only
  "watch": ["src", "config"],

  // Add 'ts' to the extension list — Nodemon now triggers on .ts file changes too
  "ext": "ts,js,json",

  "ignore": [
    "src/**/*.test.ts",
    "src/**/*.spec.ts",
    "dist/*",           // TypeScript compiles TO dist/ — never watch compiled output
    "node_modules/*",
    "logs/*"
  ],

  // 'exec' is the key that replaces 'node' with a different runtime/executor.
  // Here we use 'tsx' — dramatically faster than ts-node for large codebases.
  // Install it with: npm install --save-dev tsx
  // For ts-node instead: "exec": "ts-node src/server.ts"
  "exec": "tsx src/server.ts",

  "delay": 500,

  "env": {
    "NODE_ENV": "development",
    "PORT": "3000"
  }
}

// ─────────────────────────────────────────────────────────
// Your package.json scripts section with TypeScript:
// ─────────────────────────────────────────────────────────
// {
//   "scripts": {
//     "start": "node dist/server.js",        // Production: runs compiled JS
//     "build": "tsc",                         // Compile TS to JS for production
//     "dev":   "nodemon"                      // Dev: nodemon reads nodemon.json automatically
//   }
// }
//
// Note: 'nodemon' with no arguments reads nodemon.json and uses the 'exec' command.
// This is cleaner than putting everything in the npm script as flags.
Output
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/* config/**/*
[nodemon] watching extensions: ts,js,json
[nodemon] starting `tsx src/server.ts`
Product API (TypeScript) running on http://localhost:3000
// Edit src/routes/products.ts and save:
[nodemon] restarting due to changes...
[nodemon] starting `tsx src/server.ts`
Product API (TypeScript) running on http://localhost:3000
Interview Gold:
Nodemon is a development-only tool. In production, Node.js process management belongs to PM2 (which handles clustering, zero-downtime restarts, and process resurrection on crash) or to your container orchestrator like Kubernetes. If an interviewer asks 'how do you handle Node.js process management in production?' and you say 'Nodemon,' you've just told them you've never shipped a real production service.
Production Insight
For TypeScript projects, tsx reduces restart time from ~10s (ts-node) to ~2s on large codebases. That's a measurable productivity gain.
Always set 'exec' in nodemon.json — don't rely on shell aliases or npm scripts flags.
Remember: production runs compiled JS, not TypeScript. Keep build and dev scripts separate.
Key Takeaway
Use exec key for TypeScript/ESM.
Prefer tsx over ts-node for speed.
Keep production start script with plain node.

Nodemon in Docker and Team Workflows

Running Nodemon inside a Docker container is common for development, but it introduces a critical gotcha: file change detection across volume mounts.

On Linux hosts running Docker natively, filesystem events (inotify) propagate correctly. Nodemon works out of the box. But on macOS (Docker Desktop) and Windows, the host filesystem is mounted via a network-like layer (osxfs on Mac, SMB on Windows). These layers do not forward inotify/FSEvents signals into the container. Nodemon sees nothing. You edit a file on your host, the container's filesystem updates, but Nodemon's watcher receives no event. You stare at a stale server.

The fix is polling mode. Set '"legacyWatch": true' in nodemon.json. This tells Nodemon's chokidar to poll the filesystem every few seconds instead of waiting for events. It uses more CPU (about 2-5% on a modern machine) but it's reliable across all Docker environments. Set the polling interval via '"pollingInterval": 1000' (milliseconds) to control frequency.

Another team workflow concern is consistent Nodemon configuration across multiple developers. The only way to guarantee this is to commit a nodemon.json file to your repository. Do not rely on each developer having the right global config or CLI flags. The nodemon.json file is checked in, versioned, and every team member sees exactly the same behaviour. If someone needs a local override (e.g., different port), they can use environment variables instead of modifying the committed config.

nodemon.docker.jsonJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// io.thecodeforge — JavaScript tutorial

// nodemon.json for Docker development on macOS/Windows
// Enables polling mode to work around missing filesystem events

{
  "watch": ["src", "config"],
  "ext": "js,json,env",
  "ignore": [
    "src/**/*.test.js",
    "src/**/*.spec.js",
    "logs/*",
    "node_modules/*",
    "coverage/*",
    "dist/*",
    ".git/*"
  ],
  "delay": 500,

  // Enable polling mode for Docker volume mount compatibility
  "legacyWatch": true,

  // Poll every 1000ms (default is 1000 if not set)
  "pollingInterval": 1000,

  "env": {
    "NODE_ENV": "development",
    "PORT": "3000"
  },

  "verbose": false
}

// ─────────────────────────────────────────────────────────
// Dockerfile.development snippet (optional):
// ─────────────────────────────────────────────────────────
// FROM node:20-alpine
// WORKDIR /app
// COPY package*.json ./
// RUN npm install
// COPY . .
// # Run with polling mode already set in nodemon.json
// CMD ["npm", "run", "dev"]
Output
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/* config/**/*
[nodemon] legacy watch mode (polling) enabled
[nodemon] watching extensions: js,json,env
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
// Edit a file on host — change is detected within ~1 second (polling interval)
Docker Pro Tip:
If Nodemon works perfectly on your Linux machine but fails on a colleague's Mac, the culprit is always legacyWatch mode. Add it to the project’s nodemon.json preemptively — it adds a tiny CPU cost but eliminates an entire class of 'works on my machine' problems.
Production Insight
Polling mode uses about 3% CPU on a 4-core machine — negligible for development, but avoid in production (which you wouldn't use Nodemon for anyway).
If you have a team of 10, and half use macOS, committing a polling-enabled nodemon.json saves an estimated 2 hours per new developer onboarding.
Never assume filesystem events work across Docker volume mounts. Document the platform-specific config in README.
Key Takeaway
For Docker on macOS/Windows, set 'legacyWatch': true.
Polling uses ~3% CPU but works everywhere.
Always commit nodemon.json to repo for team consistency.
● Production incidentPOST-MORTEMseverity: high

Silent Stale Code on Docker for Mac

Symptom
Developers on macOS using Docker Compose with a Node.js container run 'npm run dev' inside the container. They edit source files on the host, save, but Nodemon never triggers a restart. The server continues running old code.
Assumption
Everyone assumed Nodemon's default file-watching mechanism works identically across all platforms. It doesn't. Docker for Mac uses osxfs, which does not forward inotify events from the host into the container.
Root cause
Nodemon's default file watcher relies on inotify (Linux kernel) or FSEvents (macOS) internally. When running inside a Docker container on macOS, the host macOS sends file changes through a network filesystem layer that doesn't relay native filesystem events. Nodemon's chokidar library never receives the change signal.
Fix
Enable 'legacyWatch' mode in nodemon.json with a polling interval. This forces Nodemon to poll the filesystem on a timer instead of relying on event-driven notifications.
Key lesson
  • If Nodemon works on native Linux but not in Docker on macOS, the fix is almost always legacyWatch: true in nodemon.json.
  • Always test your development Docker setup with a known file change before a full sprint. Catch this on day one, not day five.
  • Document platform-specific configurations in your project README so every new developer doesn't have to rediscover this.
Production debug guideCommon scenarios where Nodemon appears to ignore file changes or restarts too often4 entries
Symptom · 01
Nodemon shows no restart after saving a file
Fix
Check if legacyWatch mode is required (Docker for Mac/Windows). Run with --legacy-watch flag. If that fixes it, add '"legacyWatch": true' to nodemon.json.
Symptom · 02
Nodemon restarts continuously without any file saves
Fix
Set '"verbose": true' in nodemon.json. Look for the file path printed after 'restarting due to changes...'. Add that path or its parent directory to the 'ignore' array.
Symptom · 03
Nodemon restarts only on .js changes, not .ts or .env
Fix
Check the 'ext' key in nodemon.json. It must include 'ts', 'json', 'env' etc. Default is 'js,mjs,cjs,json'.
Symptom · 04
Nodemon starts but never runs the correct entry file
Fix
Verify the 'exec' key in nodemon.json or the command passed to nodemon. For TypeScript projects, ensure tsx or ts-node is installed and referenced.
★ Nodemon Debug Cheat SheetFive common Nodemon problems and exactly what to run to fix them
No restart on file save
Immediate action
Run nodemon with polling: nodemon --legacy-watch server.js
Commands
nodemon --legacy-watch --verbose server.js
Add '"legacyWatch": true' to nodemon.json
Fix now
echo '{"legacyWatch": true}' > nodemon.json && nodemon
Infinite restart loop+
Immediate action
Stop nodemon (Ctrl+C). Check which directory is being written to.
Commands
nodemon --verbose server.js 2>&1 | grep -i 'restarting due to changes'
Add the offending directory to nodemon.json ignore array
Fix now
Add to ignore: '"ignore": ["logs/*"]' then restart
TypeScript file changes not triggering restart+
Immediate action
Verify ext includes 'ts' in nodemon.json
Commands
nodemon --ext ts,js,json --exec tsx src/server.ts
Move configuration to nodemon.json with exec and ext keys
Fix now
Create nodemon.json with '"ext": "ts,js,json", "exec": "tsx src/server.ts"'
Nodemon not found after npm install+
Immediate action
Check package.json devDependencies have nodemon
Commands
npm ls nodemon
npx nodemon server.js (runs from local node_modules)
Fix now
npm install --save-dev nodemon && npx nodemon server.js
Rapid restarts when saving multiple files+
Immediate action
Add a delay of 500ms to batch changes
Commands
nodemon --delay 500ms server.js
Add '"delay": 500' to nodemon.json
Fix now
echo '{"delay": 500}' > nodemon.json and restart
Feature / AspectNodemonNode.js --watch (built-in, Node 18+)
Installation requiredYes — npm install --save-dev nodemonNo — built into Node.js 18+
Configuration file supportFull nodemon.json with ignore, delay, exec, envLimited — flags only, no dedicated config file
Custom executor (e.g. tsx, ts-node)Yes — via 'exec' key in nodemon.jsonNo — only runs node itself
File extension filteringYes — 'ext' key, granular controlBasic — --watch-path flag only
Ignore patternsGlob patterns in 'ignore' arrayNot supported as of Node 20
Restart debounce / delayYes — 'delay' key in millisecondsNo — restarts immediately on every change
Manual restart command ('rs')Yes — type 'rs' in terminal to force restartNo
TypeScript / ESM project supportYes — via custom 'exec' commandOnly plain .js and .mjs files
Maturity / community10+ years, battle-tested, 25M+ weekly downloadsExperimental in Node 18, stable in Node 22 but limited
Best forAny real project, especially TypeScript or complex setupsQuick one-off scripts, zero-dependency environments

Key takeaways

1
Nodemon belongs in devDependencies only. The moment it appears in a production dependency list or a production process manager, something has gone wrong with your deployment pipeline.
2
The infinite restart loop
where Nodemon restarts because the running app writes to a watched directory — is the most common Nodemon-related incident on development teams. Add your logs/, dist/, and coverage/ directories to the 'ignore' array in nodemon.json before you ever hit this.
3
Reach for nodemon.json the moment your project has more than one developer or more than two configuration flags. Inline flags in npm scripts don't scale, can't be documented inline, and create config drift between team members.
4
Nodemon is a development convenience tool, not a production process manager. If you're thinking about restart behaviour, crash recovery, or zero-downtime deploys in production
you want PM2, systemd, or your container orchestrator. Nodemon has never been designed for that job and won't do it reliably.
5
On macOS or Windows with Docker, enable legacyWatch polling mode in your nodemon.json proactively. It adds ~3% CPU overhead but saves hours of debugging silent missed restarts.

Common mistakes to avoid

5 patterns
×

Installing Nodemon as a regular dependency (npm install nodemon without --save-dev)

Symptom
Nodemon ends up in production Docker images, inflating container size and introducing file-watching overhead on live servers where it should never run. It also gets deployed to production and may restart on file writes, dropping connections.
Fix
Move it to devDependencies manually in package.json, or reinstall with 'npm install --save-dev nodemon' and remove from dependencies. Verify with 'npm ls --prod' to confirm Nodemon is not in production.
×

Watching a directory that the running app writes to (e.g., logs folder inside src/)

Symptom
Nodemon restarts infinitely: '[nodemon] restarting due to changes...' appears every few seconds with no user-triggered saves. CPU spikes and logs become unreadable.
Fix
Add the offending directory to the 'ignore' array in nodemon.json (e.g., '"ignore": ["logs/*"]'). Run Nodemon with '"verbose": true' first to identify exactly which file is causing the phantom restarts.
×

Putting Nodemon configuration as CLI flags in package.json scripts instead of a nodemon.json file

Symptom
The script becomes unreadable: 'nodemon --watch src --ext ts,js --delay 500 server.js'. Teammates add conflicting flags, and the configuration isn't version-controlled cleanly. New developers don't know what flags are expected.
Fix
Move all flags to a nodemon.json file at the project root and run just 'nodemon' in the npm script. Nodemon reads the config file automatically, ensuring consistency across all developers.
×

Using Nodemon in production (accidentally via a 'start' script that calls nodemon, or by copying the 'dev' script)

Symptom
Any file change on the production server (log rotation, deployment file writes, config updates) triggers a restart mid-request, causing dropped connections and 503 errors. CPU usage also spikes due to polling.
Fix
Ensure 'start' in package.json always uses plain 'node', and configure your production process manager (PM2, systemd, or Kubernetes) to use the start script, never the dev script. Add a CI check that fails if 'nodemon' appears in 'dependencies' or 'scripts' with 'start'.
×

Not setting a debounce delay for multi-file saves

Symptom
When a developer saves multiple files at once (e.g., during a refactor), Nodemon restarts multiple times in rapid succession, causing the server to be unavailable for several seconds. Each restart kills and re-establishes database connections, wasting time.
Fix
Add '"delay": 500' (or higher) to nodemon.json. This debounces changes: if multiple files are saved within 500ms, only one restart occurs.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Nodemon watches for file changes and restarts your Node.js process — but...
Q02SENIOR
When would you choose Nodemon over Node.js's built-in --watch flag intro...
Q03SENIOR
If a team reports that their Node.js dev server is restarting every 2-3 ...
Q01 of 03SENIOR

Nodemon watches for file changes and restarts your Node.js process — but what's the underlying mechanism it uses to detect changes, and what are the implications of that mechanism on network file systems like NFS or Docker volume mounts on Windows?

ANSWER
Nodemon uses the chokidar library under the hood, which relies on native filesystem events: inotify on Linux, FSEvents on macOS, and ReadDirectoryChangesW on Windows. On network file systems like NFS or Docker volume mounts on macOS/Windows, these native events may not propagate reliably. The fallback is polling mode (legacyWatch), where chokidar periodically checks file modification times instead of waiting for events. Polling is CPU-intensive but cross-platform reliable. The key insight is that developers on macOS using Docker must enable 'legacyWatch' in nodemon.json, or file changes will silently be ignored.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Does nodemon work with TypeScript projects?
02
What's the difference between nodemon and node --watch?
03
How do I stop nodemon from restarting when I don't want it to?
04
Is it safe to run nodemon in a Docker container during development?
05
Should I commit nodemon.json to version control?
🔥

That's Node.js. Mark it forged?

6 min read · try the examples if you haven't

Previous
NVM — Node Version Manager: Install and Switch Node Versions
17 / 18 · Node.js
Next
MERN Stack: MongoDB, Express, React, and Node.js