NodeJSBuilt-in Watch Mode (--watch)

Built-in Watch Mode (--watch)

Since Node.js 18.11 (stable in 19+), you can restart a Node process automatically when source files change by passing the --watch flag — no nodemon installation required. Node watches the files it has require()d or import()ed and restarts the process when any of them change. There's also --watch-path for watching specific directories and fs.watch() for programmatic file watching without restarting.

Basic usage

Bash
# Restart server.js whenever any of its imported files change:
node --watch server.js

# Node 22+: also supports TypeScript via --experimental-strip-types:
node --watch --experimental-strip-types server.ts

# Watch a specific directory (not just imported files):
node --watch-path=./src server.js

# Combine with --env-file:
node --watch --env-file=.env server.js
Restarting 'server.js'
Server listening on :3000
(file change detected)
Restarting 'server.js'
Server listening on :3000
--watch vs nodemon

Feature

node --watch

nodemon

Installation

None — built in

npm install --save-dev nodemon

File watching

Files actually required/imported

Glob patterns (configurable)

Watch config file

Not supported

nodemon.json or package.json key

Extension filtering

Not supported directly

-e ts,js,json

Delay before restart

Not configurable

--delay 500ms

Exec non-Node commands

No

Yes — nodemon --exec ts-node server.ts

Node version required

18.11+

Any

Use `node --watch` for simple TypeScript/JavaScript projects; keep nodemon for projects needing glob patterns, extension filtering, or non-Node exec
`node --watch` watches only files that are actually loaded via `require()` or `import()` — not directories or arbitrary globs. This is actually an advantage: it never restarts for changes to files that aren't in the module graph (e.g., test files, documentation). The limitation is that it doesn't watch files that aren't imported yet — if your code lazily imports a module, changes to that file before the first import aren't detected. For most Express/Fastify development servers, `node --watch` is sufficient and removes a dependency. For TypeScript, use it with `tsx --watch` (which uses esbuild) or wait for `--experimental-strip-types` to stabilize.
With tsx for TypeScript

Bash
# tsx has its own --watch mode (preferred for TypeScript):
npx tsx --watch src/server.ts

# Or use node --watch with tsx as the loader:
node --watch --import tsx/esm src/server.ts

JSON
// package.json scripts:
{
  "scripts": {
    "dev": "tsx --watch src/server.ts",
    "dev:node": "node --watch --env-file=.env src/server.js"
  }
}
Programmatic file watching with fs.watch

TS
import fs from 'node:fs'
import path from 'node:path'

// Watch a single file:
const watcher = fs.watch('./config.json', (eventType, filename) => {
  console.log(`${filename} changed (${eventType})`)
  reloadConfig()
})

// Watch a directory recursively (Node 19.9+ on all platforms):
const dirWatcher = fs.watch('./src', { recursive: true }, (eventType, filename) => {
  if (filename?.endsWith('.ts')) {
    console.log(`TypeScript file changed: ${filename}`)
  }
})

// Always stop watching on cleanup:
process.on('SIGTERM', () => {
  watcher.close()
  dirWatcher.close()
})
fs.watch is not fully consistent across operating systems — on macOS it uses kqueue (limited recursive support pre-Node 19.9), on Linux inotify, on Windows ReadDirectoryChangesW; use chokidar for cross-platform reliability
`fs.watch` delegates to the OS file notification API, and the behavior differs: recursive watching wasn't available on macOS before Node 19.9 (`{ recursive: true }` was silently ignored), event types (`rename` vs `change`) fire inconsistently across OSes, and some editors write files in ways that trigger multiple events or rename events instead of change events. For production-grade file watching in tools (build systems, task runners), use `chokidar` which normalizes behavior across platforms. For simple dev-server restart scenarios, `node --watch` or `tsx --watch` are the right tools.
Graceful restart with --watch
--watch sends SIGTERM before restarting — implement graceful shutdown to avoid dropping in-flight requests during development restarts
When `--watch` detects a file change, it sends `SIGTERM` to the running process before restarting. If your server doesn't handle `SIGTERM`, it gets a hard kill. For development this is usually fine — just implement the graceful shutdown pattern from the [production prep page](/nodejs/preparing-for-production) once and it handles both development restarts and production deploys correctly. The pattern: `process.on('SIGTERM', () => { server.close(callback) })` drains in-flight requests, then exits. Node `--watch` waits for the process to exit before starting the new one.
Next
Restrict what your Node process can access with the experimental permission model: [The Permission Model](/nodejs/permission-model).