watchify vs onchange vs npm-watch vs nodemon
File Watchers and Live Reloading Tools for Node.js Development
watchifyonchangenpm-watchnodemonSimilar Packages:

File Watchers and Live Reloading Tools for Node.js Development

nodemon, npm-watch, onchange, and watchify are all command-line tools designed to monitor file changes in a project and trigger actions—most commonly restarting a Node.js server or rebuilding assets. While they share the high-level goal of improving developer feedback loops during local development, they differ significantly in scope, architecture, configurability, and integration patterns. These tools sit at different layers of the development workflow: some focus exclusively on process restarts (nodemon), others tie into npm scripts (npm-watch), some offer generic cross-platform file watching with shell command execution (onchange), and one is tightly coupled to Browserify’s module bundling pipeline (watchify). Understanding their technical boundaries and trade-offs is essential when designing efficient, maintainable local development environments.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
watchify353,4331,784-376 years agoMIT
onchange190,323826-56 years agoMIT
npm-watch149,98333114.6 kB182 years agoMIT
nodemon026,662219 kB137 months agoMIT

File Watchers Compared: nodemon, npm-watch, onchange, and watchify

When you’re building applications in Node.js or JavaScript, waiting for manual restarts or rebuilds after every code change kills momentum. That’s where file watchers come in—they automate the feedback loop so you can focus on coding. But not all watchers are built the same. Let’s break down how nodemon, npm-watch, onchange, and watchify actually work under the hood, where they shine, and where they fall short.

🔄 Core Purpose: What Each Tool Actually Does

nodemon is a process monitor. It watches your filesystem and restarts a Node.js process whenever relevant files change. It doesn’t care about bundling, transpiling, or browser reloads—it just kills and respawns your server.

# Typical nodemon usage
nodemon server.js

npm-watch is a script orchestrator. It adds a watch command to npm that lets you define which files should trigger which npm scripts. It’s essentially syntactic sugar over writing your own watcher logic inside package.json.

{
  "scripts": {
    "start": "node server.js",
    "watch": "npm-watch"
  },
  "watch": {
    "start": "src/**/*.js"
  }
}

onchange is a generic file watcher. It runs any shell command you give it when files matching your glob patterns change. It’s language-agnostic and doesn’t assume anything about your stack.

# Rebuild docs on Markdown changes
onchange 'docs/*.md' -- npm run build-docs

watchify is a bundler plugin. It wraps Browserify and enables incremental bundling—only reprocessing files that have changed since the last build. It’s not a general-purpose watcher; it’s deeply integrated into Browserify’s module resolution.

# Watchify rebuilds bundle.js incrementally
watchify src/index.js -o dist/bundle.js

⚙️ Architecture: How They Manage Processes and State

nodemon spawns your target script as a child process and manages its lifecycle. When a file changes, it sends a SIGUSR2 signal (configurable) to allow graceful shutdown before restarting. It maintains an internal file cache to avoid duplicate restarts and uses chokidar under the hood for reliable cross-platform file watching.

npm-watch doesn’t manage processes directly. Instead, it parses your package.json, finds watch configs, and launches separate nodemon-like watchers for each script—but implemented with simpler file polling in older versions. This can lead to race conditions or missed events in complex setups.

onchange uses chokidar too, but it treats every command as a fire-and-forget operation. If your command takes longer than the debounce delay (default 100ms), you might end up with overlapping executions. There’s no built-in mechanism to kill previous runs—so if you’re compiling assets, you could get corrupted output.

watchify leverages Browserify’s internal caching. On the first run, it builds a full dependency graph. On subsequent changes, it invalidates only the affected modules and their dependents, then patches the output bundle. This makes rebuilds extremely fast—but only within Browserify’s ecosystem.

🛠️ Configuration: Flexibility vs Simplicity

nodemon offers rich configuration via CLI flags, nodemon.json, or even programmatic APIs. You can ignore specific files, throttle restarts, set custom signals, and even pipe stdin to the child process.

// nodemon.json
{
  "ignore": ["tests/**", ".git"],
  "delay": "2500",
  "signal": "SIGTERM"
}

npm-watch keeps config inside package.json. This is convenient for simple cases but becomes unwieldy when you need advanced options like debounce delays or custom ignore rules. It also doesn’t support nested globs well on Windows.

onchange uses command-line arguments for everything. You can chain multiple patterns, set delays, and even run commands in parallel—but complex logic requires shell scripting.

onchange 'src/**/*.{js,ts}' 'assets/*.css' --delay 500 -- npm run build

watchify inherits Browserify’s plugin/transform system. You configure it exactly like Browserify, with the added -v flag for verbose logging. But you can’t easily add non-Browserify tasks (like restarting a server) without combining it with another tool.

🌐 Ecosystem Fit: Where Each Tool Belongs Today

nodemon remains the gold standard for backend Node.js development. If you’re writing an Express app, GraphQL server, or CLI tool, it’s still the right choice. It’s actively maintained and handles real-world edge cases like Docker volume mounts or WSL file systems.

npm-watch feels dated in 2024. Most teams have moved to dedicated task runners (like concurrently + nodemon) or bundler-integrated dev servers. Its tight coupling to npm scripts limits composability, and it hasn’t kept pace with modern file-watching best practices.

onchange fills a niche for lightweight, cross-stack automation. Need to regenerate a README when source comments change? Run tests when fixtures update? It’s perfect for those glue tasks—but don’t rely on it for critical rebuild pipelines.

watchify is effectively legacy unless you’re maintaining a Browserify-based project. With the rise of ES modules, Vite, and esbuild, Browserify’s relevance has sharply declined. New projects should avoid it entirely.

🧪 Real-World Trade-Offs: What the Docs Won’t Tell You

  • Memory leaks: nodemon’s child process management prevents accumulation of zombie processes. onchange and npm-watch can leak memory if long-running commands aren’t properly terminated.

  • Windows support: nodemon and onchange (via chokidar) handle Windows path separators and drive letters correctly. Older versions of npm-watch struggled here.

  • Build correctness: watchify guarantees consistent bundles because it’s part of the bundler. Generic watchers like onchange might trigger mid-write, leading to partial file reads.

  • Debugging: nodemon forwards stdin/stdout seamlessly, so console.log and debugger statements work as expected. Other tools may buffer or drop output.

💡 When to Combine Tools

In practice, you often need more than one watcher:

  • Use nodemon to restart your API server.
  • Use onchange to regenerate OpenAPI specs when route files change.
  • Avoid npm-watch—it rarely adds value over composing simpler tools.
  • Only reach for watchify if you’re stuck in a Browserify world.

✅ Bottom Line

  • Backend Node.js apps? → nodemon
  • Simple npm script watching? → Consider skipping npm-watch; use nodemon or concurrently instead
  • Ad-hoc file-triggered tasks? → onchange
  • Browserify projects? → watchify (but plan to migrate)

Choose based on what you’re actually trying to automate—not just what’s easiest to install.

How to Choose: watchify vs onchange vs npm-watch vs nodemon

  • watchify:

    Choose watchify only if you’re already using Browserify to bundle your JavaScript and want incremental rebuilds during development. It integrates directly with Browserify’s transform pipeline and caches unchanged modules to speed up rebuilds dramatically compared to full rebundling. Do not use it if you’ve migrated to modern bundlers like Webpack, Vite, or esbuild, as it offers no value outside the Browserify ecosystem.

  • onchange:

    Choose onchange if you need a lightweight, cross-platform watcher that can execute arbitrary shell commands in response to file changes across any part of your stack—frontend, backend, or tooling scripts. Its glob-based pattern matching and support for multiple concurrent watchers make it flexible for custom workflows, but you’ll need to handle process management (like killing stale builds) yourself, as it doesn’t manage child processes beyond spawning them.

  • npm-watch:

    Choose npm-watch if you prefer to keep your development automation tied directly to your package.json scripts and want minimal additional tooling. It extends npm’s native script system by allowing you to define watch configurations alongside your existing scripts, making it easy to adopt without learning new syntax. However, it lacks advanced file filtering, event debouncing, or cross-platform reliability guarantees found in more specialized watchers.

  • nodemon:

    Choose nodemon if you're developing a Node.js application (like an Express API or CLI tool) and need a simple, battle-tested utility that automatically restarts your process when source files change. It handles common edge cases like ignoring node_modules by default, supports graceful shutdowns via signals, and offers fine-grained control over watched paths and restart conditions through CLI flags or config files. It’s not suitable for frontend asset pipelines or browser reloading.

README for watchify

watchify

watch mode for browserify builds

build status

Update any source file and your browserify bundle will be recompiled on the spot.

example

$ watchify main.js -o static/bundle.js

Now as you update files, static/bundle.js will be automatically incrementally rebuilt on the fly.

The -o option can be a file or a shell command (not available on Windows) that receives piped input:

watchify main.js -o 'exorcist static/bundle.js.map > static/bundle.js' -d
watchify main.js -o 'uglifyjs -cm > static/bundle.min.js'

You can use -v to get more verbose output to show when a file was written and how long the bundling took (in seconds):

$ watchify browser.js -d -o static/bundle.js -v
610598 bytes written to static/bundle.js (0.23 seconds) at 8:31:25 PM
610606 bytes written to static/bundle.js (0.10 seconds) at 8:45:59 PM
610597 bytes written to static/bundle.js (0.14 seconds) at 8:46:02 PM
610606 bytes written to static/bundle.js (0.08 seconds) at 8:50:13 PM
610597 bytes written to static/bundle.js (0.08 seconds) at 8:58:16 PM
610597 bytes written to static/bundle.js (0.19 seconds) at 9:10:45 PM

usage

Use watchify with all the same options as browserify except that -o (or --outfile) is mandatory. Additionally, there are also:

Standard Options:

  --outfile=FILE, -o FILE

    This option is required. Write the browserify bundle to this file. If
    the file contains the operators `|` or `>`, it will be treated as a
    shell command, and the output will be piped to it.

  --verbose, -v                     [default: false]

    Show when a file was written and how long the bundling took (in
    seconds).

  --version

    Show the watchify and browserify versions with their module paths.
Advanced Options:

  --delay                           [default: 100]

    Amount of time in milliseconds to wait before emitting an "update"
    event after a change.

  --ignore-watch=GLOB, --iw GLOB    [default: false]

    Ignore monitoring files for changes that match the pattern. Omitting
    the pattern will default to "**/node_modules/**".

  --poll=INTERVAL                   [default: false]

    Use polling to monitor for changes. Omitting the interval will default
    to 100ms. This option is useful if you're watching an NFS volume.

methods

var watchify = require('watchify');

watchify(b, opts)

watchify is a browserify plugin, so it can be applied like any other plugin. However, when creating the browserify instance b, you MUST set the cache and packageCache properties:

var b = browserify({ cache: {}, packageCache: {} });
b.plugin(watchify);
var b = browserify({
  cache: {},
  packageCache: {},
  plugin: [watchify]
});

By default, watchify doesn't display any output, see events for more info.

b continues to behave like a browserify instance except that it caches file contents and emits an 'update' event when a file changes. You should call b.bundle() after the 'update' event fires to generate a new bundle. Calling b.bundle() extra times past the first time will be much faster due to caching.

Important: Watchify will not emit 'update' events until you've called b.bundle() once and completely drained the stream it returns.

var fs = require('fs');
var browserify = require('browserify');
var watchify = require('watchify');

var b = browserify({
  entries: ['path/to/entry.js'],
  cache: {},
  packageCache: {},
  plugin: [watchify]
});

b.on('update', bundle);
bundle();

function bundle() {
  b.bundle()
    .on('error', console.error)
    .pipe(fs.createWriteStream('output.js'))
  ;
}

options

You can to pass an additional options object as a second parameter of watchify. Its properties are:

opts.delay is the amount of time in milliseconds to wait before emitting an "update" event after a change. Defaults to 100.

opts.ignoreWatch ignores monitoring files for changes. If set to true, then **/node_modules/** will be ignored. For other possible values see Chokidar's documentation on "ignored".

opts.poll enables polling to monitor for changes. If set to true, then a polling interval of 100ms is used. If set to a number, then that amount of milliseconds will be the polling interval. For more info see Chokidar's documentation on "usePolling" and "interval". This option is useful if you're watching an NFS volume.

var b = browserify({ cache: {}, packageCache: {} });
// watchify defaults:
b.plugin(watchify, {
  delay: 100,
  ignoreWatch: ['**/node_modules/**'],
  poll: false
});

b.close()

Close all the open watch handles.

events

b.on('update', function (ids) {})

When the bundle changes, emit the array of bundle ids that changed.

b.on('bytes', function (bytes) {})

When a bundle is generated, this event fires with the number of bytes.

b.on('time', function (time) {})

When a bundle is generated, this event fires with the time it took to create the bundle in milliseconds.

b.on('log', function (msg) {})

This event fires after a bundle was created with messages of the form:

X bytes written (Y seconds)

with the number of bytes in the bundle X and the time in seconds Y.

working with browserify transforms

If your custom transform for browserify adds new files to the bundle in a non-standard way without requiring. You can inform Watchify about these files by emiting a 'file' event.

module.exports = function(file) {
  return through(
    function(buf, enc, next) {
      /*
        manipulating file content
      */
      
      this.emit("file", absolutePathToFileThatHasToBeWatched);
      
      next();
    }
  );
};

install

With npm do:

$ npm install -g watchify

to get the watchify command and:

$ npm install watchify

to get just the library.

troubleshooting

rebuilds on OS X never trigger

It may be related to a bug in fsevents (see #250 and stackoverflow). Try the --poll flag and/or renaming the project's directory - that might help.

watchify swallows errors

To ensure errors are reported you have to add a event listener to your bundle stream. For more information see (browserify/browserify#1487 (comment) and stackoverflow)

Example:

var b = browserify();
b.bundle()
  .on('error', console.error)
   ...
;

see also

  • budo – a simple development server built on watchify
  • errorify – a plugin to add error handling to watchify development
  • watchify-request – wraps a watchify instance to avoid stale bundles in HTTP requests
  • watchify-middleware – similar to watchify-request, but includes some higher-level features

license

MIT