Here is an example that illustrates how you can exchange an app in a running express server. It serves as an illustration, not as production-grade code!
Run node
with the following code:
var app = express().use(function(req, res, next) {
setTimeout(function() {
res.end("App version 1");
}, 5000);
});
express().use(function(req, res, next) {
app(req, res, next);
}).listen(80);
readline.createInterface(process.stdin).on("line", function(line) {
line = line.split(" ");
if (line[0] === "upgrade") app = require("./" + line[1]);
});
and have a newer version of the app ready in a separate file (appv2.js
, say):
module.exports = express().use(function(req, res, next) {
res.end("App version 2");
});
Now if you type upgrade appv2
(plus ENTER) in the console of your running node
process, it will replace app
with the newer version, while the long-running (5 seconds) requests from the app version 1 still finish. Only requests received after the upgrade will be executed in app version 2.
If app version 2 needs an asynchronous initialization step, this should be completed before the switch from version 1 to version 2. This makes the upgrade command asynchronous. The file appv2.js
would then look like
var app = express().use(function(req, res, next) {
...
});
module.exports = async function init() {
// Initialization of app
return app;
};
and the upgrade command becomes
readline.createInterface(process.stdin).on("line", async function(line) {
line = line.split(" ");
if (line[0] === "upgrade") app = await require("./" + line[1])();
});
With this approach, there is no server downtime: Requests that were received before the app = ...
statement finishes behave according to version 1, requests after that behave according to version 2. Of course, this assumes that everything the app needs to know is contained in the appv2.js
file. In the likely case that the app uses a database, and this database has to be upgraded as well, things become more complicated.