How to Run a Node.js App Locally: Built-in Watch Mode for Efficient Development
Use Node.js's built-in --watch flag to automatically restart your application on file changes without installing external dependencies like nodemon.
When you start Node.js project development locally, having a fast feedback loop is essential for productivity. According to the Node.js source code, the runtime now includes a native watch mode that supervises your process and handles automatic restarts, eliminating the need for external tools while integrating deeply with the core module system.
How Node.js Watch Mode Works
The built-in watch implementation lives entirely within the core codebase and operates with zero runtime overhead when disabled. Here is how the system processes your --watch flag:
CLI Option Parsing
When you append --watch to your command, Node.js first parses this through lib/internal/options.js. The options module exposes flags including --watch, --watch-path, --watch-preserve-output, and --watch-kill-signal via the getOptionValue function.
The Watch Mode Supervisor
If --watch is present, control passes to lib/internal/main/watch_mode.js instead of the standard entry point. This module serves as a supervisor that prepares a clean execution environment and removes watch-related flags from process.execArgv before building the command array for the child process.
Process Spawning and Monitoring
In the start() function (lines 94-100 of watch_mode.js), the original Node process spawns your application (node app.js) as a child using child_process.spawn. The child inherits the parent's environment plus WATCH_REPORT_DEPENDENCIES=1, enabling dependency tracking.
File System Watching
The supervisor instantiates FilesWatcher from internal/watch_mode/files_watcher.js to monitor paths specified by --watch-path. If no path is provided, the watcher operates in filter mode using the kShouldFilterModules flag, watching only modules that your application actually imports. When a file changes, the watcher emits a changed event.
Graceful Restart Logic
Upon detecting changes, the restart() function (lines 61-73) sends the signal defined by --watch-kill-signal (defaulting to SIGTERM) to the child process. The supervisor waits up to 500ms for a graceful exit via reportGracefulTermination(), then re-spawns the child. Unless you specify --watch-preserve-output, the console clears between restarts.
Setting Up Your Local Development Workflow
Basic Watch Mode Setup
Add a development script to your package.json that points to your entry file:
{
"scripts": {
"dev": "node --watch app.js"
}
}
Run npm run dev to start the supervisor. The process will restart automatically whenever any imported module changes.
Advanced Configuration Options
Watch specific directories to avoid spurious restarts from unrelated files:
{
"scripts": {
"dev": "node --watch --watch-path=src app.js"
}
}
Preserve console output across restarts when debugging initialization logic:
{
"scripts": {
"dev": "node --watch --watch-preserve-output app.js"
}
}
Customize the termination signal if your application requires specific shutdown handling:
{
"scripts": {
"dev": "node --watch --watch-kill-signal=SIGINT app.js"
}
}
Debugging with Watch Mode
Combine watch mode with the inspector to attach a debugger on each restart:
{
"scripts": {
"debug": "node --inspect --watch app.js"
}
}
Practical Code Examples
Run these commands directly in your terminal for different development scenarios:
# Basic watch - restarts on any module dependency change
node --watch app.js
# Watch only the src directory
node --watch --watch-path=src app.js
# Keep console history visible between restarts
node --watch --watch-preserve-output app.js
# Debug mode with custom kill signal
node --inspect --watch --watch-kill-signal=SIGINT app.js
Complete package.json configuration example:
{
"name": "my-app",
"main": "app.js",
"scripts": {
"dev": "node --watch --watch-path=src --watch-preserve-output app.js",
"debug": "node --inspect --watch app.js"
}
}
Summary
- Native watch mode in
lib/internal/main/watch_mode.jsprovides dependency-free file watching and process management. - Zero overhead when disabled, with deep integration into Node's module loader and signal handling.
- Granular control via
--watch-path,--watch-preserve-output, and--watch-kill-signalflags. - Graceful restarts wait 500ms for cleanup before respawning the child process.
- Debugger compatibility allows
--inspectto work seamlessly with automatic restarts.
Frequently Asked Questions
What is the difference between --watch and nodemon?
Node.js's built-in --watch requires zero dependencies and integrates directly with the core runtime's module system, whereas nodemon is an external package that adds abstraction layers. The native implementation in lib/internal/main/watch_mode.js specifically handles WATCH_REPORT_DEPENDENCIES and uses the internal FilesWatcher class, providing more predictable behavior aligned with your Node.js version.
How do I prevent restarts when unrelated files change?
Use the --watch-path flag to specify exact directories, or omit it entirely to enable the kShouldFilterModules filter mode. In filter mode, the watcher only monitors files that are actually imported by your application, ignoring unrelated changes in the filesystem.
Can I use watch mode in production?
While technically possible, watch mode is designed for local development. The process spawning logic in lib/internal/main/watch_mode.js creates a supervisor-child relationship that adds complexity unnecessary for production deployments. Use process managers like systemd, PM2, or container orchestration for production reliability.
Why does my app take 500ms to restart?
The restart() function in lib/internal/main/watch_mode.js implements a 500ms grace period to allow reportGracefulTermination() to complete cleanup operations. If your process exits immediately upon receiving SIGTERM, the supervisor proceeds immediately; otherwise, it waits for the timeout before forcefully respawning the child.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →