If you want to "restart everything" then the top-voted answers are fine, but if you're using Node.js in 2025 and you want to hot reload your module code those solutions are completely useless.
Instead, you're going to have to slightly change how you work with objects so that you can use dynamic imports (i.e., the import() function, not the import keyword) to update your already instantiated objects.
I've documented this extensively over on Flying planes with JavaScript, Hot-reloading to make our dev lives easier, but repeating it here:
We can set up a file watcher that uses "cache busting", because you can't unload modules the same way you can clear the legacy require cache:
// We'll be using Node.js' own file watch mechanism for this:
import { watchFile } from "node:fs";
// With some helpers for making sure we know where our files and modules live:
import { __root } from "./constants.js";
import { rootRelative } from "./utils.js";
export async function watch(basePath, modulePath, onChange) {
const filePath = basePath + modulePath;
const moduleURL = `file:///${filePath}`;
// Step 1: don't run file-watching in production. Obviously.
if (process.env.NODE_ENV === `production`) {
return import(moduleURL);
}
// Next, get the current callstack, so we can report on
// that when a file change warrants an update.
const callStack = new Error().stack
.split(`\n`)
.slice(2)
.map((v) => {
return v
.trim()
.replace(`file:///${__root}`, `./`)
.replace(/^at /, ` in `)
.replace(/new (\S+)/, `$1.constructor`);
})
.join(`\n`)
.replace(/\.constructor \(([^)]+)\)(.|[\n\r])*/, `.constructor ($1)`);
// If we're not running in production, check this file for changes every second:
watchFile(filePath, { interval: 1000 }, async () => {
console.log(`Reloading module ${rootRelative(filePath)} at ${Date.now()}`);
try {
// If there was a change, reimport this file as an ES module, with a "cache busting" URL
// that includes the current time stamp. Modules are cached based on their exact URL,
// so adding a query argument that we can vary means we can "reimport" the code:
const module = await import(`${moduleURL}?ts=${Date.now()}`);
// Then we log the stack so we know where this reload was set up in our code:
console.log(callStack);
// To confirm to ourselves that a module was fully loaded as a "new module" we check
// whether it has a `LOAD_TIME` constant that it set during load, and log what that
// value is. Because it should be very close to our reload time.
if (module.LOAD_TIME)
console.log(` Module-indicated load time is ${module.LOAD_TIME}`);
// And then we run whatever code needs to run now that the module's been reloaded.
onChange(module);
} catch (e) {
// Never crash the server just because someone saved a file with a typo.
console.error(`\nWatcher could not load module: ${filePath}`);
console.error(callStack);
console.error(e);
}
});
// Then, as part of the call, run an immediate load with a timestamp, so we're cache-busting.
return import(`${moduleURL}?ts=${Date.now()}`);
}
Of course you can house those constants and utils imports however you like, but their associated code is:
// Get the directory that this file lives in:
import url from "node:url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
// And then use that to figure out what the project root dir is:
import { join, resolve, sep, win32, posix } from "node:path";
export const __root = (resolve(join(__dirname, `..`, `..`)) + sep).split(win32.sep).join(posix.sep);
and
import { win32, posix } from "node:path";
// Get a file's path relative to the project root directory
export function rootRelative(filepath) {
return filepath.split(win32.sep).join(posix.sep).replace(__root, `./`);
}
With that set up, we can now implement true hot reloading, where our code keeps running but object classes and function references get updated as we work on our files. For instance, let's say we have an index.js file like this:
// Get the URL for "this script", since that's what relative imports will be relative to:
import url from "node:url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
// Then import our watcher:
import { watch } from "./reload-watcher.js";
// We can set up our reload watcher to update not just the
// variable for our class, but also instances of that class:
let { Something } = await watch(__dirname, `some-module.js`, (lib) => {
// Update our class binding:
Something = lib.Something;
// And then update our class instance's prototype. This only works because
// by the time this code runs, the "instance" variable will exist.
if (instance) {
Object.setPrototypeOf(instance, Something.prototype);
// This swaps the old prototype for the new code, while leaving any
// of the instance's own properties unaffected. Something that will be
// particularly useful for changing autopilot code *while* we're flying!
}
});
let instance = new Something();
At this point we use that instance like we would any other class instance, but its prototype will get updated every time we save.
Is that quite a bit more work than nodemon or supervisor? Absolutely. But it's only work "up front", and in return you get true hot reloading instead of a full program restart.
Although there is one caveat: JavaScript does not officially have a module unloading mechanism, so you will be slowly filling up your memory. With an emphasis on that "slowly": you'd need to hot reload tens of thousands of times to start noticing. And of course, it's dev-only. Even if you leave it in, it won't hot reload in production (plus: what would even be touching your files in production?)
Object.keys(require.cache).forEach(function(key) { delete require.cache[key]; });app.jsas a server file should be enough?