Looking Outside Ruby: Node.js
In this, the latest in the Looking Outside Ruby series, we’re going to have a look at server-side JavaScript with Node.
What is Node?
Node is a set of libraries that enable JavaScript to run outside the browser, where the JavaScript in question is the V8 engine from Chrome. It’s main aim is to make it simple and easy to build network clients and servers. Node was started back in 2009 by Ryan Dahl and the project is currently sponsored by Joyent, his employer.
Why use it?
In a nutshell: speed and scalability. The V8 JavaScript engine that is at the heart of Node is known for it’s speed, and the “evented I/O” approach used by Node makes it scalable and able to handle many concurrent connections with ease.
Evented I/O
Node uses an event loop architecture, the design of which was partly influenced by Ruby’s Event Machine. If you’re not familiar with event loops, they’re defined on Wikipedia as:
a programming construct that waits for and dispatches events or messages in a program. It works by polling some internal or external “event provider”, which generally blocks until an event has arrived, and then calls the relevant event handler (“dispatches the event”)….The event loop almost always operates asynchronously with the message originator.
Basically, Node is constantly checking for events occurring, be that receiving a HTTP request or accessing a file on the disk. When a given event fires, it’s event-handling callback function is executed.
This type of event handling happens to us a lot in everyday life. How many times have you phoned someone and had to leave a message for them to call you back? While your waiting for them to callback you’re free to get on with other things.
One of the examples used in Ryan Dahl’s slides from his 2009 talk at JSConf.eu looks at database access:
var result = db.query("select * from T");
// use result
The pertinent question that is then asked is: what is your software doing while it queries the database? The answer is: usually nothing. The same code implemented in an event loop might look something like this:
db.query("select...", function(result) {
// use result
});
The program can return to the event loop straight away and doesn’t have to wait for the database query to finish. When it does finish the event loop will dispatch the callback function.
Node runs in a single thread, just as JavaScript runs in a single thread in the browser. As such, you should try to make sure that all I/O in your Node code is non-blocking – you don’t want to be blocking the event loop by doing some CPU-intensive computations or synchronous I/O. Fortunately, Node comes with a set of core modules that provide a non-blocking interfaces to access things such as file and network resources.
Installation
Node source code is available on OS X and Linux, and there’s an node.exe you can download for Windows. You can install node via a package manager; at the time of writing packages exist for Debian, Ubuntu, openSUSE, Arch Linux, homebrew and macports on OS X, and chocolatey on Windows.
If you want to build Node yourself on OS X or Linux you’ll need to fire up a command-line and type the following:
curl -O http://nodejs.org/dist/node-v0.4.12.tar.gz
tar xzvf node-v0.4.12.tar.gz
cd node-v0.4.12.tar.gz
./configure --prefix=/usr/local/node
make
make install
NB Here we’re installing Node into /usr/local
but, if you’re comfortable with building packages from source code, feel free to change this as you see fit.
Once Node is installed, set your $NODE_PATH
environment variable so that Node knows where to look for modules:
echo 'export NODE_PATH=/opt/node:/opt/node/lib/node_modules' >> ~/.bashrc # ~/.bash_profile or ~/.profile on some systems
If /usr/local/node/bin
isn’t already in your $PATH
you can add it like so:
echo 'export PATH=$PATH:/opt/node/bin' >> ~/.bashrc # ~/.bash_profile or ~/.profile on some systems
Then type in the following to reload your new $PATH
:
. ~/.bashrc # ~/.bash_profile or ~/.profile on some systems
Writing code
Let’s start by looking at the canonical “hello world” program, this time in the form of a web server:
var http = require('http');
http.createServer(function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!');
}).listen(4567, '127.0.0.1');
console.log('Server running at http://127.0.0.1:4567');
You can either save this code in a file called something like server.js
and run it by typing node server.js
in your terminal, or alternatively just type node
at the command-prompt to start node in interactive mode – the equivalent of Ruby’s irb
– and copy-and-paste the code into the terminal. In this code we’re:
- Importing the http module using the
require
function - Calling the http module’s
createServer
function and passing it a callback function; this function will be fired every time Node receives a new connection - Telling Node which port and IP address we want our server to run / listen on
- Logging a message to the console so that we know our server is up and running
Remember earlier when we mentioned Node’s scalability?
the “hello world” web server…many client connections can be handled concurrently. Node tells the operating system (through epoll, kqueue, /dev/poll, or select) that it should be notified when a new connection is made, and then it goes to sleep. If someone new connects, then it executes the callback. Each connection is only a small heap allocation.
— from http://nodejs.org/#about
Emitting and Handling your own Events
No matter how much you use Node’s core modules, there will come a time when you need to emit and handle your own – or someone else’s – custom events. Node comes with an EventEmitter class that helps us do this. The two main methods it has that we’re interested in here are on
and emit
. When wiring up and handling our custom events, we can inherit from EventEmitter to attach these methods to our code. Lets have a look at a simple server to illustrate this:
var util = require('util'),
EventEmitter = require('events').EventEmitter;
var Server = function() {
console.log('init');
};
util.inherits(Server, EventEmitter);
var s = new Server();
s.on('error', function() {
console.log('error...');
});
...
s.emit('error');
In this example:
- We use the
util
module’sinherits
method to make our Server class inherit from the EventEmitter - We setup a callback function for the ‘error’ event by calling the now inherited
on
method - Calling
emit
on our Server instance will cause the error callback function to fire
Node Modules
Node uses the CommonJS module system. The goal of CommonJS is to provide a common API for JavaScript on the server that can be used regardless of the JavaScript interpreter, so the same code that runs on Node can also be run without modification on Rhino. CommonJS also defines how modules can be loaded using require
and how you need to make use of the exports
object when writing your own modules.
If you look at the earlier example code, you’ll see that we used require
to import some of the core modules. CommonJS stipulates that each module will have access to the require
function, and that require
should return the exported API of the module (more on that shortly). When you pass require
the name of a module, you can either pass it the name of a ‘top-level’ module or a ‘relative’ module i.e. the module identifier starts with a .
or ..
.
Now, what was meant by “exported API”? Well, CommonJS also states that each module will have access to the exports
object, and you define your module’s API by explicitly adding functions and variables to it; any functions and variables that aren’t added to exports
remain private to the module.
We can illustrate this by writing our own module, jedimindtrick
. As Node uses a one-file-per-module approach, save the code below in a file called jedimindtrick.js
:
// jedimindtrick.js
var droid1 = 'R2-D2',
droid2 = 'C-3PO';
exports.mindTrick = function(d1, d2) {
if (droid1 === d1 && droid2 === d2) {
console.log("These aren't the droids you're looking for");
}
}
moveAlong = function() {
console.log("Move along...move along");
}
Then, at the command-line, change directory so that you’re in the same folder where you just saved jedimindtrick.js
, type node
and hit <Enter>
to fire up Node’s interactive mode, and type in following:
var jedi = require('./jedimindtrick');
jedi.mindTrick('R2-D2', 'C-3PO');
jedi.moveAlong();
In this code we’re passing the relative path to our module to require
, calling the exported function mindTrick
, then attempting to call the moveAlong
function. But, as this wasn’t added to the exports
object in our module, we’ll cause a TypeError: Object [object Object] has no method 'moveAlong'
.
Hosting
Finally, a quick word on hosting. The Node wiki has a list of hosting providers. The good news for Ruby programmers is that Node hosting is provided by Heroku.
Well, that was a quick look at Node. We’ve looked at what Node is and the potential advantages it offers, how you can install it, been through some short code examples and had a quick look at Node modules and how you can write your own. It is by no means a comprehensive guide, but hopefully, if you haven’t yet had a look at Node, it’ll have encouraged you to do so.
Further Reading
- Official Node site: http://nodejs.org/
- Node on GitHub (includes the Node wiki): https://github.com/joyent/node
- npm – Node Package Manager: http://npmjs.org/