Thursday, December 09, 2010

Having fun with NodeJS

This is rather a memo for myself than tutorial of building a simple Web site using server-side JavaScript (read why you may need this).

I have registered a domain name, and purchased a 'Level 1' VPS hosting from HostGator (with pre-installed CentOS 5.5), but you can try this on a virtual machine running on your desktop as well.

1. Let's start from installing node.js:

# Download the source package:
wget http://nodejs.org/dist/node-v0.2.5.tar.gz

# Build & Install:
tar -zxf node-v0.2.5.tar.gz
cd node-v0.2.5
./configure
make
sudo make install

2. Now, we need to install a package manager for node.js:

# It's recommended to run npm from a regular user, so we just make the /usr/local/ directory writable by the group 'wheel', and add the relevant user to this group:

sudo chgrp -R wheel /usr/local/{share/man,bin,lib/node}
sudo usermod -g -G wheel $USER

# Install npm:
curl http://npmjs.org/install.sh | sh

3. As developer of node.js himself states, node.js isn't production ready yet. What he suggests is running node.js behind a reverse proxy, served by nginx, for example.

a) Installing nginx is very easy on CentOS:

# Add EPEL repository:
rpm -i http://download.fedora.redhat.com/pub/epel/5/x86_64/epel-release-5-4.noarch.rpm

# Install 'nginx':
yum install nginx

b) Let's configure nginx proxy:

# First, comment out the server {...} section in /etc/nginx/nginx.conf:

vim /etc/nginx/nginx.conf

# Second, add the proxy configuration to /etc/nginx/conf.d/virtual.conf (we suppose that your node.js application will be running on port 8000):

cat <<EOF >> /etc/nginx/conf.d/virtual.conf
upstream app_cluster_1 {
        server 127.0.0.1:8000;
}

server {
        listen 0.0.0.0:80;
        server_name node.local node;
        access_log /var/log/nginx/node.log;

        location / {
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Host $http_host;
          proxy_set_header X-NginX-Proxy true;

          proxy_pass http://app_cluster_1/;
          proxy_redirect off;
        }
}
EOF

4. Create your simple Web application:

a) We will use ExpressJS as a Web framework, so we need to install it first:

npm install express

b) Let's install templates engine as well. I've tried to use Haml template engine, but I couldn't make it render pages properly, so I was forced to move to using ejs (and I don't regret that):

npm install ejs

c) Create the application folders:

mkdir /var/www/apps/your_app

# Static content will be placed here:
mkdir /var/www/apps/your_app/static

# Views (templates) folder:
mkdir /var/www/apps/your_app/views

Here are files that we are going to create under these folders:

/var/www/apps/your_app/app.js

var sys = require('sys');
var express = require('express');
var app = express.createServer();

app.configure(function(){
        app.use(express.methodOverride());
        app.use(express.bodyDecoder());
        app.use(app.router);
        app.use(express.staticProvider(__dirname + '/static'));
        app.set('views', __dirname + '/views');
        //app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

var site_locals = {
        copyright: 'Copyright @ Michael Spector 2010',
};

app.get('/', function(req, res){
        res.render('hello.ejs', {
                locals: { site: site_locals },
        });
});

app.listen(8000);

/var/www/apps/your_app/views/layout.ejs

<html>
        <head>
                <title>My App</title>
                <link rel="stylesheet" href="/style.css" />
        </head>
        <body>
              <!-- Pay an attention to this special construct that will be replaced with the actual view contents (hello.ejs in our case): -->
              <%- body %>
              <hr/>
              <%= site.copyright %>
        </body>
</html>

/var/www/apps/your_app/views/hello.ejs

<h1>Hello, World!<h1/>

/var/www/apps/your_app/static/style.css

body {
   text-align: center;
}

5. The last this is making sure that if node.js unexpectedly dies, it will be started again automatically. For this purpose we will install monit:

# Install monit:
yum install monit

# Uncomment "set httpd" entires in the main configuration file:
vim /etc/monit.conf

# Create the configuration file for your application (note that we define NODE_ENV=production variable prior running node.js, which should enable all production features, like caching, etc...):

cat <<EOF > /etc/monit.d/your_app
check host objdump with address 127.0.0.1
    start program = "/bin/sh -c 'NODE_ENV=production /usr/local/bin/node /var/www/apps/your_app/app.js'"
        as uid nobody and gid nobody
    stop program  = "/usr/bin/pkill -f 'node /var/www/apps/your_app/app.js'"
    if failed port 8000 protocol HTTP
        request /
        with timeout 10 seconds
        then restart
EOF

6. Finally, configure all services to start automatically when system boots, and start them:

sudo chkconfig nginx on
sudo chkconfig monit on

/etc/init.d/nginx restart
/etc/init.d/monit restart

monit stop your_app
monit start your_app

If you need to restart your application upon re-deployment, run:

monit restart your_app

Go to http://<your-ip>/, and have fun!

Hope this tutorial helps you.