Normally when I host my Node.js-based applications, I’ll SSH into my server, open up a screen or tmux session, run node ./server.js, detach, and call it a day. Of course, if you’re reading this article, you’re fully aware that this is a horrible solution and are looking for an alternative.
One thing that is going to change between the hacky method of hosting and this new method is that it won’t be YOU that is executing the process, but instead the SYSTEM. So, we’ll be taking a few extra steps here and there to enforce that concept.
For starters, we don’t want to execute Node directly (even once we set up our service). Instead, we want to run it behind some sort of service that will keep the process alive in the unfortunate event of a crash. There are many tools for this, such as monit, PM2, or nodemon but the one I’m most familiar with is called forever. Feel free to use an alternative if you’d like.
First, we’ll want to install forever on the server. Run the following command to take care of that:
sudo npm install -g forever
Once you’ve got forever installed, it’s a good idea to have it throw pid files and log files somewhere. I threw a directory into /var/run for just this purpose (although I’m not sure if this is technically the best place for such a thing):
sudo mkdir /var/run/forever
If you’re used to storing your Node.js projects in your home directory (like I was…), you need to stop! Instead, store them somewhere which makes more sense as far as the entire server is concerned. The directory /var is pretty good for doing this, and if your application serves up HTML, throwing it in /var/www is probably a good idea.
I host a lot of applications and websites on my server, so I put my sites directories like /var/www/example.org.
Run Time Configuration
There are a few changes you may want to make to your application to make it server-friendly. One thing I always find myself needing to do is pass a port number that I want my process to listen on. My server has a few IP addresses (network interfaces) and sometimes I’ll also need to pass in which interface I want to bind to.
A common solution for this is to pass along command line arguments. A different approach that I’ve been liking lately is to set environment variables (environment variables are analogous to named parameters and CLI arguments to normal function arguments).
A quick note on Node.js server listening conventions: Whether you’re using the built in http module, or going with express or other similar frameworks, the convention is that you call a .listen() method on the main http object you’re working with. The first argument is a port number, and the second argument is a hostname. If you don’t provide a hostname or pass in null, it defaults to listening on all interfaces (e.g. ‘0.0.0.0’). If you pass in the string ‘localhost’ or ‘127.0.0.1’, the port can only be accessed from the local machine. If you pass in the ip address of one of your interfaces, it will only listen on that interface.
Here’s an example of how you might implement both of these methods in your scripts:
Command Line Arguments
./server.js 9000 "localhost"
var app = require('express')();
var port = parseInt(process.argv, 10) || 80;
var interface = process.argv || null;
SERVER_PORT=9000 SERVER_IFACE="localhost" ./test.js
var app = require('express')();
var port = process.env.SERVER_PORT || 80;
var interface = process.env.SERVER_IFACE || null;
Now for the fun part! First, create yourself an empty init script, substituting the word SERVICE for the name you want to use for the service:
sudo touch /etc/init.d/SERVICE
sudo chmod a+x /etc/init.d/SERVICE
sudo update-rc.d SERVICE defaults
Once that’s done, paste the following simple service template into the file, swapping out SERVICE to whatever you’d like to use:
case "$1" in
exec forever --sourceDir=/var/www/SERVICE-p /var/run/forever start server.js
exec forever stop --sourceDir=/var/www/SERVICE server.js
This script went with the environment variable method of configuration. Since we’re dealing with a bash script, I threw the variables at the top of the script instead of on the same line as the command we executed for added readability. Of course, if you adopted the command line argument method, omit the two export lines and add your arguments to the end of your command.
If you’d like to start (or even stop) the service, you can run the same old commands that you’re likely used to:
sudo service SERVICE start
sudo service SERVICE stop
Consider the Following
Now that you’ve got everything setup, your service should be able to survive a reboot of your machine! Go ahead and run sudo init 6 right now just to be sure. Just kidding.
If you ever want a list of your currently running applications, run sudo forever list. Read up on the forever documentation to see what else you can do (hint: log reading).
That Debian service script we wrote is a bit lacking! If you check out the contents of /etc/init.d/skeleton, you can get an idea of a more robust script.