If you’ve built anything with Node.js, you’ve probably used nodemon to automatically restart your app when files change. It’s been a go-to developer tool for years — but did you know you no longer need it?

With Node.js 18.11 and above, there’s a built-in --watch option. It tells Node to monitor your source files and automatically restart the process when changes are detected.

node --watch app.js

That’s it. No configuration, no dependencies, no waiting for external tools to kick in.

It works great when:

  • You’re building small or medium apps.
  • You don’t need advanced features like custom delay or file filters.
  • You want to keep your environment minimal and lean.

Keep in mind that, large projects with complex file structures might still benefit from nodemon or more advanced tools.

What Node actually watches 👀#

This is the part nobody explains, and it’s the reason --watch sometimes looks broken.

Node doesn’t watch your folder. It watches your entry point and every file that entry point requires or imports — the module graph, in other words. node_modules is excluded by default, which is why the whole thing stays fast.

So this restarts:

node --watch app.js

…when you edit app.js, or routes/users.js, or anything else your app actually pulls in.

While it’s running you’ll see Node narrating itself in the terminal:

Completed running 'app.js'. Waiting for file changes before restarting...
Restarting 'app.js'

What it does not restart on is a file nothing imports. Edit an HTML template, a .sql seed file, a JSON fixture you read with fs.readFile instead of import, and Node sits there doing nothing while you refresh the page and wonder what you broke. I lost a solid ten minutes to this before the penny dropped: the file was never part of the graph, so it was never being watched.

Watching folders Node can’t see: --watch-path#

That’s what --watch-path is for. It takes a path — pass the flag more than once for more than one path:

node --watch --watch-path=./src --watch-path=./views app.js

Now edits inside views/ restart the app even though nothing in your code imports them.

One thing to know: the entry point still has to be there. If you forget it, Node stops you with a refreshingly clear message:

node: --watch requires specifying a file

Bonus: you can probably drop dotenv too#

Since Node 20.6.0 there’s --env-file, which loads a .env file straight into process.env — no package, no require('dotenv').config() at the top of your file. It stopped being experimental in Node 22.21.0 and 24.10.0.

node --watch --env-file=.env app.js

And here’s the nice part I only found by testing it: editing .env restarts the app too. The env file counts as a watched file, so your new value is live on the next boot without you touching anything.

If a variable exists both in your real shell environment and in the file, the shell wins — the file doesn’t override what’s already set. That’s deliberate, and it’s what you want on a server.

More on all of this in our guide to environment variables with dotenv.

node --watch vs nodemon, honestly 🥊#

node --watch nodemon
Install Built in since 18.11 npm install --save-dev nodemon
Watches by default Entry point + imported modules js, mjs, coffee, litcoffee, json in the working directory
Extra folders --watch-path --watch
Ignore rules --ignore lib/ --ignore tests/
Delay before restart --delay 2500ms
Run something that isn’t Node --exec "python -v" ./app.py
Manual restart type rs and hit return
Config file nodemon.json, or nodemonConfig in package.json
Keep old logs on restart --watch-preserve-output on by default

That last row is worth a sentence. By default --watch clears your terminal on every restart, which is lovely until the error you needed to read scrolls into the void. --watch-preserve-output (available since 18.11, same release as --watch itself) turns that off:

node --watch --watch-preserve-output app.js

So when should you still install nodemon?#

Be honest about which of these you actually need:

  • You’re not running Node. nodemon --exec "python -v" ./app.py watches files and restarts anything. --watch only restarts Node.
  • Your files save in bursts. A build step that writes forty files triggers forty restarts. --delay debounces that; Node has no equivalent.
  • You need to ignore things. Test folders, generated output, a tmp/ directory your app writes to on every request. Without an ignore rule that last one is an infinite restart loop.
  • You want the rs escape hatch. Typing rs to force a restart is a genuinely useful habit when a watcher gets confused.

If none of those describe you — and for most small Express apps and scripts, none of them do — that’s one dependency you never have to install again.

Frequently asked questions#

What Node version do I need for --watch?#

Node 18.11.0 or newer. It was experimental at first and is now marked stable, so anything on a current LTS release has it. Check with node -v.

Does --watch work with TypeScript?#

Not on its own — --watch restarts a Node process, it doesn’t compile anything. Recent Node versions can run TypeScript files by stripping the types, and for a full build step you still want tsc --watch alongside, or nodemon --exec driving your compiler. If you’re setting a project up from scratch, our Express.js server with TypeScript walkthrough covers the setup.

How do I use --watch in an npm script?#

Exactly the same way, in package.json:

{
  "scripts": {
    "dev": "node --watch --env-file=.env app.js"
  }
}

Then npm run dev. This is usually where nodemon lived, so it’s a one-line swap.

Why does my app restart in a loop?#

Something your app writes is inside what Node is watching — a log file, a cache, a generated file in src/. The app writes, the watcher fires, the app restarts, it writes again. Move the file out of the watched path, or switch to nodemon and add --ignore for it.

Can I still use --watch in production?#

Please don’t. Watch mode is a development convenience — it holds file handles open and restarts your process on disk activity, which is the last thing you want on a live server. Use a process manager there instead.

Keep reading#