12 — Keep the App Running in Development --watch and Nodemon
"Just save and re-run the command" was my dev-loop model, and the restart cost quietly ate my attention. The model that finally stuck is one fact and one consequence: Node.js loads code once at startup and does not reload it, so the only way a code change takes effect is to restart the process. That makes a file-watcher-that-restarts-for-me the single biggest quality-of-life tool in Node development, and as of recent versions that watcher ships with Node itself [1].
The framing that landed for me is "Node caches modules, so changes need a restart," then the two ways to get that restart for free.
Why a watcher is necessary
Node.js evaluates a module the first time it is required or importd, then caches the result in memory. Subsequent imports return the cached object — they do not re-read the file. This caching is essential for performance and module identity, but it has a direct cost during development: after I edit a file, the running process is still executing the old code. Unlike a browser hot-reloading CSS, Node has no built-in mechanism to swap in new module code while the process runs.
The practical consequence is that, without tooling, every save means: switch to the terminal, kill the process, re-run the command. Multiply that by a hundred saves an hour during active development, and it is a serious drag. The fix is a process that watches the file system for changes and restarts the Node process automatically. Two tools do this job.
Node's built-in --watch flag
As of Node 18.11.0 (stabilized in Node 19+), the --watch flag is built in [1][2]. It watches the entry file and its imported modules, and whenever one of them changes, it restarts the script — no external dependency required.
node --watch server.jsThat is the whole interface. I edit a file, save, and the process restarts within milliseconds. Because it is built into Node, there is no npm install step, no separate config file, and no version drift between the watcher and the runtime. For new projects, this is now my default — the dev script in package.json is just node --watch server.js, and it works the same on every machine.
The flag has grown over versions — --watch-path to restrict which directories are watched, and integration with the inspector for keeping debug sessions across restarts. The Node docs are the source of truth for the current surface [2].
Nodemon: the long-standing external tool
Nodemon is the external watcher that predates the built-in flag by over a decade, and it is still everywhere in existing projects [3][4]. It wraps the Node process, watches the file system, and restarts on change.
npx nodemon server.js{
"scripts": {
"dev": "nodemon server.js"
}
}Nodemon's appeal over the raw flag is configuration — a nodemon.json file lets me specify which extensions to watch (js,json,ts), which directories to ignore (node_modules, dist), what command to run (a different runtime, a custom exec), and delay before restarting (to batch rapid saves). For a project with non-standard file types or a complex startup command, that configurability earns its keep.
The honest assessment today: for a plain Node.js project, the built-in --watch flag has absorbed most of what Nodemon was used for. Nodemon remains the right call when I need its config file or when a project's tooling (older Express apps, custom runners) is built around it. But starting fresh, --watch is one fewer dependency to maintain.
What the watcher does (and does not) do
A watcher restarts the process. That means application state in memory is lost on every restart — in-memory caches reset, database connections re-open, long-running jobs are interrupted. This is fine for a dev server (I want fresh state on each change) but it is the reason a watcher is a development tool, not a production one. In production, restart-on-file-change is the wrong model entirely — the right tools there (PM2, process managers) keep the app alive across crashes, not across saves.
The watcher also does not, by itself, do hot module replacement — swapping a single module without losing state, the way some frontend bundlers do. It is a full process restart, every time. For a server, that is acceptable; for a UI, it would be too slow, which is why the frontend world built more sophisticated tooling on top.
How I use this
The model I keep is "Node caches modules, so a code change needs a restart, and a watcher gives me that restart for free." For every new project, the dev script is node --watch <entry>, because it is built in and zero-config. For existing projects that already use Nodemon, I leave Nodemon in place — migrating gains nothing material. The one habit I keep regardless of tool: I configure the watcher to ignore node_modules, dist, and any generated output, because a generated file changing should not restart the dev server. Watching source files only keeps restarts meaningful. The way of thinking — process restart, not live reload — is what sets the right expectation for what the watcher does.
References
[1] K. Esmail, "Built-in Node.js Watch Mode," Medium, 2023. [Online]. Available: https://medium.com/@khaled.smq/built-in-nodejs-watch-mode-52ffadaec8a8
[2] OpenJS Foundation, "Node.js CLI: --watch," Node.js API Docs. [Online]. Available: https://nodejs.org/api/cli.html#--watch
[3] "Nodemon," nodemon.io. [Online]. Available: https://nodemon.io/
[4] DigitalOcean, "How To Restart Your Node.js Apps Automatically with nodemon." [Online]. Available: https://www.digitalocean.com/community/tutorials/workflow-nodemon
Knowledge check · Question 1 of 4
Why does editing a Node.js source file not affect the already-running process?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!