Managing Node.js servers on Mac OS X with forever - works best for development

; Date: 2014-01-03 17:07

Tags: Node.JS

If, like me, you're doing Node.js development on a Mac, you might have a yearning for a tool like MAMP but which works for Node.  A couple weeks ago I wrote a blog post covering the first step, (nodejs.davidherron.com) setting up a Node and npm instance on your computer.   If you don't know what MAMP is, go read that blog post, and then come back here.  What I want to go over today is a way to manage/monitor one or more Node processes on your computer.

(github.com) Forever is a simple CLI-oriented tool to ensure that a Node process runs continuously.  It's functionality is similar to the init daemon on Linux systems, except it doesn't run at system boot-up time.  On a Mac, launchd serves the same purpose.  Out of the box forever doesn't integrate with either, but it's possible to write a little wrapper script.

Installation is straightforward

$ sudo npm install forever -g

While forever has an API, we'll be using it as a command line tool.  Type this to get a list of options:

$ forever -help

It has start, stop and restart commands to manage processes, as well as a list command to show the current processes.

Among the (github.com) example scripts that demonstrate using forever is a simple server that we can play with:

var util = require('util'),
    http = require('http'),
    argv = require('optimist').argv;

var port = argv.p || argv.port || 8080;

http.createServer(function (req, res) {
  console.log(req.method + ' request: ' + req.url);
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.write('hello, i know nodejitsu.');
  res.end();
}).listen(port);

/* server started */
util.puts('> hello world running on port ' + port);

Note that I've modified it to run on port 8080, because on the Mac an Apache process is running on port 80 (assuming you've enabled Web Sharing in the control panel).

A required bit of setup is to run this:

$ npm install optimist

And then you start the server this way:

$ forever start server.js
warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: server.js

And then you can visit (localhost) http://localhost:8080/ in your browser.  If you have multiple applications you're developing it's simple enough to ensure that, for development, they're running on different ports, eh?  Maybe.

You can query the status of servers this way:

$ forever list
info:    Forever processes running
data:        uid  command             script    forever pid   logfile                        uptime       
data:    [0] 4NdQ /opt/local/bin/node server.js 48843   48844 /Users/david/.forever/4NdQ.log 0:0:2:11.320

The [0] indicates the script index, and is used in some of the forever commands to identify the script to act on.  For example we can view the logfile this way:

$ forever logs 0
data:    server.js:48844 - > hello world running on port 8080

Or stop the server:

$ forever stop 0
info:    Forever stopped process:
data:        uid  command             script    forever pid   logfile                        uptime       
[0] 4NdQ /opt/local/bin/node server.js 48843   48844 /Users/david/.forever/4NdQ.log 0:0:4:11.846
$ forever list
info:    No forever processes running

Since this is about developing Node applications, we want to be able to edit our code and automatically reload the application.  Forever makes this fairly easy, with two different methods.

One way is:

$ forever restart 0

Which restarts and reloads the application.  However, we can instruct forever to watch and automatically reload changes:

$ forever start -w server.js server.js

Then after making a change, we can reload the page and see the change, and also see this in the logs.

$ forever logs 0
data:    server.js:49219 - > hello world running on port 8080
data:    server.js:49219 - GET request: /
data:    server.js:49219 - error: restarting script because /Users/david/t/server.js changed
data:    server.js:49219 - error: Forever detected script was killed by signal: SIGKILL
data:    server.js:49219 - error: Forever restarting script for 1 time
data:    server.js:49219 - > hello world running on port 8080

While running the processes look like this:

$ ps -eaf | grep node
501 48939     1   0  3:48PM ??  0:00.50 /opt/local/bin/node /opt/local/lib/node_modules/forever/bin/monitor server.js
501 48943 48939   0  3:49PM ??  0:00.12 /opt/local/bin/node /Users/david/server.js

Notice that the parent process of the forever/bin/monitor.js process is process#1 which is /etc/init.  It means we can log out, log back in, and the process will still be there.  However, we reboot the system and the process doesn't restart.  Maybe we want to use forever to manage this.

I found a solution over on stackoverflow - (stackoverflow.com) http://stackoverflow.com/questions/18604119/osx-launchd-plist-for-node-forever-process

One uses a plist of this shape:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>KeepAlive</key>
        <dict>
            <key>SuccessfulExit</key>
            <false/>
        </dict>
        <key>Label</key>
        <string>com.davidherron.testapp</string>
       <key>ProgramArguments</key>
        <array>
            <string>/opt/local/bin/node</string>
            <string>/opt/local/bin/forever</string>
            <string>-a</string>
            <string>-l</string>
            <string>/var/log/com.davidherron.testapp.log</string>
            <string>-e</string>
            <string>/var/log/com.davidherron.testapp_error.log</string>
            <string>-w</string>
            <string>/Users/david/t/server.js</string>
            <string>/Users/david/t/server.js</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StartInterval</key>
        <integer>3600</integer>
    </dict>
</plist>

Save it as /Library/LaunchDaemons/com.davidherron.testapp.plist then make sure it's owned correctly:

$ sudo chown root /Library/LaunchDaemons/com.davidherron.testapp.plist
$ sudo chmod 0644 /Library/LaunchDaemons/com.davidherron.testapp.plist

The idea is to be able to launch it this way:

$ sudo launchctl load /Library/LaunchDaemons/com.davidherron.testapp.plist

Unfortunately, while it runs, we're unable to view the processes using forever.  After awhile the process crashed but forever did not restart it.

After a while of ditzing around with this, it appears that using forever from launchd is not a good combination.  Maybe someone has figured out how to run forever with launchd and can leave a comment below.

In the meantime I'll say that forever is fine for development use, on a Mac.