Chapter 1. Node.js

Node.js has quickly become a very popular asynchronous framework for JavaScript. It is built on top of the same V8 engine that the Chromium and Google Chrome web browsers use to interpret JavaScript. With the addition of networking and file system API support, it has quickly proved to be a capable tool for interacting with IO in a asynchronous way.

There are many other libraries in several other languages that can accomplish the same asynchronous handling of IO. There are different conventions, schools of thought, and preferences of developers. Node.js uses callbacks for the developer to notified of the progress of asynchronous operations. Callbacks are nothing new for developers accustom to Python’s Twisted library or other similar frameworks. Callbacks can be a very easy and powerful way to manage the flow of an appilication, but as with anything new they also offer an opportunity to trip up a developer. The first thing to keep in mind when getting started with asynchronous development is that execution might not follow the same squence every time.

Getting Started with Node.js

In order to install Node.js, download the source and build it. The main Node.js web page at http://nodejs.org can be very helpful in linking to downloads, source code repositories, and documentation. The master branch of the repository is kept in a semi-unstable state, so before building check out the most recent tagged version. For example: v0.4.9.

Note

The Node.js package manager or NPM is an extremely useful tool. It can handle installing, updating, and removing packages and their dependencies. Creating packages is also simple since the configuration for the package is contained in the package.json file. Installation instructions for NPM are included in the Node.js repository.

Asynchronous Callbacks

An Example case to show how asynchronous IO works is to make two HTTP requests and then combine the results. In the first example the request to the second web API will be nested in the callback from the first. This might seem like the easiest way to combine the results, but will not be the most effective usage of asynchronous IO.

Google provides an API that returns the elevation for a given latitude and longitude. The example requests will be of two points random points on Earth. To start create a function that will handles the request to the Google elevation API as well as parses the response:

var http = require("http"),
    sys = require("sys");

function getElevation(lat,lng, callback){
    var options = {
        host: 'maps.googleapis.com',
        port: 80,
        path: '/maps/api/elevation/json?locations='+lat+','+lng+'&sensor=true'
    };
    http.get(options, function(res) {
        data = "";
        res.on('data', function (chunk) {
                data += chunk;
            });
        res.on('end', function (chunk) {
                el_response = JSON.parse(data);
                callback(el_response.results[0].elevation);
            });
    });
}

In order to run the requests sequentially, the call to fetch the second elevation is in the callback for the first:

var elevations= []
getElevation(40.714728,-73.998672, function(elevation){
        elevations.push(elevation);
        getElevation(-40.714728,73.998672, function(elevation){
                elevations.push(elevation);
                console.log("Elevations: "+elevations);
            });
});

This will add the two elevations in order to the elevations array. However, the program will wait for the first request to finish before making the second request. The amount of time fetching the two elevations can be reduced by making the initial requests in parallel and combining the results in the callback:

var elevations= [];
function elevationResponse(elevation){
    elevations.push(elevation);    if(elevations.length == 2){
        console.log("Elevations: "+elevations);
    }
}

getElevation(40.714728,-73.998672, elevationResponse);
getElevation(-40.714728, 73.998672, elevationResponse);

Now the callback checks to see if the combined data is complete; in this case, it checks to see if there are two items in the array.

Sometimes the first response callback gets called before the second, and sometimes it does not. Since the requests are carried out at the same time and they can take a variable ammount of time, it isnt guaranteed what order the callback functions will be called in. But what if this data needs to be displayed in order?

There are cases that require nesting the call to another function in a callback—perhaps if the response to the first request was going to provide the needed data to make the second request. In that case, there is no choice but to wait, and make the second request after the first.

In the elevation example, there is no need to wait. Both requests can be made at the same timea and the results can be combined later. By adding a function to correctly combine the data and using that as the response callback, the data can then be presented in the correct order every time.

By doing these two requests asynchronously, the execution time is reduced. This makes the app more responsive to the user, and frees the app to do other needed processing while waiting on IO tasks. A quick timing of the two methods show the difference in time needed to fetch the same data.

hostname $ time node elevation_request.js

Elevations: 8.883694648742676,-3742.2880859375

real        0m0.627s
user        0m0.076s
sys         0m0.029s

hostname $ time node elevation_request2.js
Elevations: 8.883694648742676,-3742.2880859375

real        0m0.340s
user        0m0.074s
sys         0m0.027s

In other languages this can be accomplished through threading, in many cases. Threads are sometimes messy to work with, as they require synchronizing or locking in order to manipulate shared memory safely. The forced Asnychronous IO of Node.js gives a clean way to accomplish parallel tasks.

Using Node.js on the Web

One of the many uses of Node.js is to serve up dynamic content over HTTP: that is to say, websites. Again another advanage of Node.js’s Asynchronous IO is the preformance of handling many requests at same time. There is a maturing list of modules and frameworks to handle some of the common tasks of a web server. ConnectJS is an HTTP server module that has a collection of plugins that provide logging, cookie parsing, session management and much more.

ExpressJS

Built on top of ConnectJS is ExpressJS framework. ExpressJS extends ConnectJS adding robust routing, view rendering, and templating. Using ExpressJS, it is easy to get a simple web server up and running. ExpressJS can be installed using npm:

hostname $ npm install express

Routes

There are only a few lines of code needed to start a server and handle a URL route:

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

app.get('/', function(req, res){
    res.send('nodejs!');
});

app.listen(3000);

Run this with Node.js:

hostname $ node app.js

This server can now be reached at http://localhost:3000/.

When setting up a route in ExpressJS, the second argument is a callback function. The callback is executed when the route matches the requested URL. The callback is passed two arguments. First, a request object that contains all the information about that HTTP request. Second a response object which has member functions that manipulate the HTTP response.

The param function on the request object parses parameters that are in the query string or in the post body. The function returns the value or an optional default value that is set using the second argument to the function:

app.get('/echo', function(req, res){
    echo = req.param("echo", "no param")
    res.send('ECHO: '+echo);
});

Templates

The response object has member functions which can be used to set the headers and the status code, return files, or simply return a text response body as above. The response object also handles rendering templates:

app.get('/template', function(req, res){
   res.render('index.ejs', { title: 'New Template Page', layout: true });
});

The above code will looks for the template named index.ejs by default in a directory named views and replaces the template variables with the set passed into the render function:

<h1><%= title %></h1>

ExpressJS supports several templating markups, and of course can be extended to support others. These include the following:

  • Haml: A haml implementation

  • Jade: The haml.js successor

  • EJS: Embedded JavaScript

  • CoffeeKup: CoffeeScript based templating

  • jQuery: Templates for node

Static Files

ExpressJS can also serve up static files such as images, client side JavaScript, and stylesheets. The first argument to the use function specifies a base route. The second argument specifics the local directory to serve static files from. In this case, files in the static directory will be accessible along the same path:

app.use('/static', express.static(__dirname + '/static'));
// This mean the file "static/client.js" will be available at
// http://localhost:3000/static/client.js

ExpressJS handles many other aspects of running a HTTP server, including session support, routing middleware, cookie parsing, and many other things. The full documentation for ExpressJS is provided at http://expressjs.com/guide.html

Node.js with its powerful asynchronous IO, common and simple syntax, and many useful modules in active development is a great choice for building web applications.

Get Getting Started with GEO, CouchDB, and Node.js now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.