How To Hot Reload NodeJs Server
Seb Toombs
Dec 19 2019 ()
4 min read
How to Hot Reload a NodeJs Server on File Changes
And a really quick lesson on the Node require.cache
Recently I was working on a NextJs project with a custom Node server. I was running the server with Nodemon, which is pretty normal, but every time I change a server file, the whole http server has to reload. And even worse as a Next server it has to spin up the Next app as well. This all meant that every file change was about a 5-10 second wait before I could test even the smallest change. Not cool!
We could probably achieve what we want using webpack-dev-server, but this would require a bit more config. Also in my use case, Next spins up webpack to run it's magic so that was a no-go.
Node Require Cache Invalidation to the Rescue!
What we're going to do is refactor our server code slightly, add a file watcher, and rely on the require cache invalidation. Remember how in Node we can include/execute/require code from another file like;
const includedModule = require('./includedModule')
Well, a little known fact is that Node maintains a cache (or store/record) of modules. As your code encounters a require statement, Node will load the module and then store it in thee require.cache.
Each subsequent load of that module will load it from the require.cache, instead of reading the file again (performance reasons yeah).
What you can do, however, is invalidate this cache, forcing Node to read the file again!
The require cache is accessible via the require.cache object, which is simply and object whose keys correspond to the loaded modules.
IMPORTANT NOTE: The require.cache keys are file paths. E.g. /users/username/project/file.js
To remove a module from the require cache and make our Node server reload it, all we have to do is simply delete the key from the object! eg;
delete require.cache['/users/username/project/file.js']
Once the key is removed from the cache, Node will reload it when it next encounters that require call.
Automatic Hot Reloading of Node Modules
Suggested File Structure
I'm going to suggest the following file structure for this. You can change this up, but make sure you change the file watcher & cache invalidation.
Project Root
- /server
- index.js
- /app
- router.js
- all other server files
Server Hot Reload
First we need to add a file watcher to trigger our cache invalidation. I'm going to go for a bit of a blanket, catch-all solution, but there are other ways to achieve this. My solution will use chokidar to watch files.
Add this code in your server/index.js file
// server/index.js
//...
// Only run this in development
if (process.env.NODE_ENV !== "production") {
const chokidar = require("chokidar");
//Set up watcher to watch all files in ./server/app
const watcher = chokidar.watch("./server/app");
watcher.on("ready", function() {
//On any file change event
//You could customise this to only run on new/save/delete etc
//This will also pass the file modified into the callback
//however for this example we aren't using that information
watcher.on("all", function() {
console.log("Reloading server...");
//Loop through the cached modules
//The "id" is the FULL path to the cached module
Object.keys(require.cache).forEach(function(id) {
//Get the local path to the module
const localId = id.substr(process.cwd().length);
//Ignore anything not in server/app
if(!localId.match(/^\/server\/app\//)) return;
//Remove the module from the cache
delete require.cache[id];
});
console.log("Server reloaded.");
});
});
}
That's the file watcher done. This code will watch for any changes to files within the server/app directory, and then remove all the cached modules in that directory from the require cache.
This will force Node to hot reload those modules when it next encounters that require call.
Server Router
The final part is to set up our server/index.js. This part caused me a couple of minutes of confusion working out why it wouldn't work. We need to refactor any server.use (or other http verb) calls in this file to execute a function which then calls require.
// server/index.js
const express = require("express");
const port = parseInt(process.env.PORT, 10) || 3000;
// ...
// File watcher could go here
// ...
const app = express();
//Hot reload!
//ALL server routes are in this module!
app.use((req, res, next) => {
require("./app/router")(req, res, next);
});
//...
app.listen(port, err => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}`);
});
This file sets up a basic Node/express server (of course you could use other server as you wish). The important part is that app.use calls a function which passes req, res, next into the require call. It might look redundant, but it's not!
And an example server/app/router.js;
const router = require("express").Router();
// Put all your server routes in here
router.use('/helloworld', (req, res) => {
res.send('Hello, World!');
});
router.use('/some-module', require('./some-module'));
//...
module.exports = router;
Now You Have Hot Reloading in Your Node Server!
You'll be coding like a ninja!
Final note: if you want to, you can still run your server/index.js with nodemon to watch for any changes there, just be aware this will cause a full restart.
Require Cache Invalidation for Production Zero-Downtime Deploys
I honestly haven't looked into whether this method of hot reloading would provide stable production zero-downtime deploys, however my "gut feel" says don't do it. And production deployments are probably one area where we should stick to known, trusted procedures unless we have good reason.
Ideas, Comments, Suggestions?
Got an idea to improve this? Comments? Suggestions? Hit me up on twitter @baffledbasti