Thursday, April 5, 2012

Node.js on Mac OS X - 30 minute guide

Today I decided to give node.js a try on Mac OS X Lion. Having no real hands-on experience in node.js, and wanting to get something end-to-end up and running fast, so I went off in search for a good hands on tutorial.

For those of you who don't know what node.js is : "Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices."

I stumbled upon a great nodejs intro on github by indexzero. that gives you a nice end-to-end introduction to nodejs and CouchDB. The sample features a REST based api to perform CRUD operations on bookmarks. It uses CouchDB as a datastore.

The goal of this post is to do the required setup in order to get the sample up and running under 30 minutes. I decided to give it a try and started by installing both node.js and CouchDB on my Mac OS X Lion. By the end of this tutorial, you'll have node.js and CouchDB installed, and will be able to run the REST based API on your OS X installation.

This article assumes you have Git installed on your machine. If you don't have Git installed, checkout the Set Up Git on Mac guide.

Installing node.js and npm

The easiest way to download and install node.js from the the nodejs download section and pickup the Macintosh installer. It will install both node and the node package manager (npm).

After the install, you'll have access to both the node and npm command.

Installing CouchDB

As the node js tutorial uses CouchDB to store objects, we'll need to install CouchDB.

Installing CouchDB is a bit more effort in the sense that we'll download the sources and compile them. Before we can do that, we first need to install Homebrew by executing the following commands :

git clone https://github.com/mxcl/homebrew.git
cd homebrew/bin
brew install autoconf automake libtool
brew install couchdb
Important note ! : There seems to be an issue with the CouchDB recipe introduced a couple of days ago that will prevent you from installing CouchDB. In order to fix this, you'll need to manually edit ~/couch/homebrew/Library/Formula/couchdb.rb

Change

require 'formula'

class Couchdb < Formula
  url 'http://www.apache.org/dyn/closer.cgi?path=couchdb/source/1.1.1/apache-couchdb-1.1.1.tar.gz'
  homepage "http://couchdb.apache.org/"
  md5 'cd126219b9cb69a4c521abd6960807a6'

into this (notice how the "source folder" needs to be removed.

require 'formula'

class Couchdb < Formula
  url 'http://www.apache.org/dyn/closer.cgi?path=couchdb/1.1.1/apache-couchdb-1.1.1.tar.gz'
  homepage "http://couchdb.apache.org/"
  md5 'cd126219b9cb69a4c521abd6960807a6'
If the installation hangs, you can continue CTRL-C the install and try it again by executing
./brew install -v couchdb
More info on the process can be found in "Installing CouchDB on OSX".

After the CouchDB compile is complete, you can start it by executing "./couchdb". You can verify that CouchDB is running by opening a browser and navigating to http://127.0.0.1:5984/_utils.

Downloading the tutorial

Now that we have everything setup, we can continue with the node js intro sample.

We start by checking out the source.

git clone https://github.com/indexzero/nodejs-intro.git 

Creating the CouchDB database

Before we can start the tutorial, we're going to have to create a CouchDB database/ We're going to be using the command line (make sure CouchDB is started) :
$ curl -X PUT http://127.0.0.1:5984/pinpoint-dev10
{"ok":true}

You can visit the CouchDB Futon http based console on http://127.0.0.1:5984/_utils. You should see your newly created database in the console.

There's an excellent CouchDB guide here.

Starting the tutorial

The node js sample is constructed in a modular way. The lib folder contains several modules that can be bootstrapped using the server script in the bin folder.

For example, to start the CouchDB tutorial, all you need to do is execute the following command from the bin folder

./server -t 02couchdb -s

The -t flag allows you to specify a module from the lib folder, the -s flag will setup the pinpoint-dev database we created earlier.

The sys - util change

Depending on the version of Node that you're using you might see the following error or warning :

$ node -v
v0.7.7-pre

$ ./server -t 02couchdb -s

node.js:247
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: The "sys" module is now called "util".
    at sys.js:1:69
    at NativeModule.compile (node.js:572:5)
    at Function.require (node.js:540:18)
    at Function._load (module.js:297:25)
    at Module.require (module.js:357:17)
    at require (module.js:373:17)
    at Object. (/home/ubuntu/nodejs-intro/bin/server:3:11)
    at Module._compile (module.js:444:26)
    at Object..js (module.js:462:10)
    at Module.load (module.js:351:32)
In order to get rid of the error, you'll need to replace all calls to `require("sys")` with `require("util")` in all the .js files you're using in your project (and its dependent modules).

Node v0.6.14 doesn't throw an error, but provides a warning that the package has been changed.

$ node -v
v0.6.14

$ ./server -t 02couchdb -s
The "sys" module is now called "util". It should have a similar interface.
Pinpoint demo server listening for 02couchdb on http://127.0.0.1:8000

Running the tutorial

When you run the tutorial, you're going to get some erros

$ ./server 02couchdb
The "sys" module is now called "util". It should have a similar interface.

node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: Cannot find module 'optimist'
    at Function._resolveFilename (module.js:332:11)
    at Function._load (module.js:279:25)
    at Module.require (module.js:354:17)
    at require (module.js:370:17)
    at Object. (/Users/ddewaele/Projects/Node/nodejs-intro/bin/server:5:12)
    at Module._compile (module.js:441:26)
    at Object..js (module.js:459:10)
    at Module.load (module.js:348:31)
    at Function._load (module.js:308:12)
    at Array.0 (module.js:479:10)
The tutorial has several dependencies that we'll need to download using the Node Package Manager (npm). The npm command comes with the nodejs installation on OS X.

Installing node packages

Node packages (dependencies) are installed using the npm like this :

$ npm install optimist
npm http GET https://registry.npmjs.org/optimist
npm http 200 https://registry.npmjs.org/optimist
npm http GET https://registry.npmjs.org/optimist/-/optimist-0.2.8.tgz
npm http 200 https://registry.npmjs.org/optimist/-/optimist-0.2.8.tgz
npm http GET https://registry.npmjs.org/wordwrap
npm http 200 https://registry.npmjs.org/wordwrap
npm http GET https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz
npm http 200 https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz
optimist@0.2.8 ../node_modules/optimist 
└── wordwrap@0.0.2

The packages will be installed in a node_modules folder in the root folder of our tutorial :

$ ls -l ../node_modules/
total 0
drwxr-xr-x  10 ddewaele  staff  340 Apr  1 18:54 optimist

the node js intro requires the following modules to be installed :

npm install winston
npm install cradle
npm install journey
npm install optimist

Running the tutorial

From the bin folder, start the tutorial by executing the following command :

$ ./server -t 02couchdb -s
The "sys" module is now called "util". It should have a similar interface.
Pinpoint demo server listening for 02couchdb on http://127.0.0.1:8000

Going to http://127.0.0.1:8000/bookmarks in a browser should return the following response :

{"bookmarks":[]}
This means our service is up and running. In order to add some data in CouchDB, we're going to be using http-console to access our REST service and send some data over the wire.

Installing http-console

A great tool to help you debug your services is something called http-console. You can install http-console through the node package manager. This time, we'll install the http-console package globally, making it available system-wide. We do this by using the -g (global) switch.

sudo npm install -g http-console

You'l notice that the http-console executable is now available on the command-line. Unfortunately, when starting http-console you'll get the following error.

$ http-console 


node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: require.paths is removed. Use node_modules folders, or the NODE_PATH environment variable instead.
    at Function. (module.js:378:11)
    at Object. (/usr/local/lib/node_modules/http-console/bin/http-console:6:8)
    at Module._compile (module.js:441:26)
    at Object..js (module.js:459:10)
    at Module.load (module.js:348:31)
    at Function._load (module.js:308:12)
    at Array.0 (module.js:479:10)
    at EventEmitter._tickCallback (node.js:192:40)

You can fix this by editing the /usr/local/lib/node_modules/http-console/bin/http-console file, and removing the following line :

require.paths.unshift(path.join(__dirname, '..', 'lib'));

After having removed the line above, http console should start. When providing no arguments, it will connect to http://localhost:8080 by default. If you want to connect to another server/port, just specify it as the first argument.

In order to connect to the server provided by the tutorial, we'll start the http-console like this (notice how we use the \json command to set the correct content-type):

$ http-console http://127.0.0.1:8000
The "sys" module is now called "util". It should have a similar interface.
> http-console 0.6.1
> Welcome, enter .help if you're lost.
> Connecting to 127.0.0.1 on port 8000.

http://127.0.0.1:8000/> \json
http://127.0.0.1:8000/> 

Accessing the REST service

Inside the http-console, executing a GET request is as simple as typing GET /bookmarks. You should get the same response as the one you saw in your browser earlier :

http://127.0.0.1:8000/> GET /bookmarks
HTTP/1.1 200 OK
Date: Sun, 01 Apr 2012 17:23:27 GMT
Server: journey/0.4.0
Content-Type: application/json;charset=utf-8
Content-Length: 16
Connection: keep-alive

{
    bookmarks: []
}

You can also execute POST commands like this by providing a JSON snippet.

http://127.0.0.1:8000/> POST /bookmarks
... { "url": "http://nodejs.org" }
HTTP/1.1 200 OK
Date: Thu, 05 Apr 2012 11:45:55 GMT
Server: journey/0.4.0
Content-Type: application/json;charset=utf-8
Content-Length: 91
Connection: keep-alive

{
    bookmark: {
        _id: 'WD-G-1',
        resource: 'Bookmark',
        url: 'http://nodejs.org'
    }
}

When executing the GET request again, as you can see, our server responds with the bookmark that has now been inserted in CouchDB.

http://127.0.0.1:8000/> GET /bookmarks
HTTP/1.1 200 OK
Date: Sun, 01 Apr 2012 17:23:27 GMT
Server: journey/0.4.0
Content-Type: application/json;charset=utf-8
Content-Length: 16
Connection: keep-alive

{
    bookmarks: [
        {
            _rev: '1-cfced13a45a068e95daa04beff562360',
            _id: 'WD-G-1',
            resource: 'Bookmark',
            url: 'http://nodejs.org'
        }
    ]
}

References