A tutorial on Node.js for dummies

 

In this world of high performance (low response time on almost real-time communication) and scalable (involving millions of user requests) Web applications, Node.js is gaining popularity everyday. Some says that Node.js is the hottest technology at Silicon Valley used by tech giants such as VMWare, Microsoft, LinkedIn, eBay, Yahoo, etc.

Let’s say what is Node.js?

In simple words, Node.js is an event-driven JavaScript runtime environment on the server-side. Node.js runs using V8 JavaScript engine developed by Google and achieves high throughput via non-blocking (asynchronous) I/O in a single-threaded event loop as well as due to the fact that V8 compiles JavaScript code at high speeds into native machine code.

The real magic behind the high performance on Node.js is the event loop. In order to scale to large volumes of user requests, all I/O intensive operations are performed asynchronously. The traditional threaded approach where for processing each user request a new thread is created is cumbersome and consumes large unnecessary resources specifically memory. To avoid this inefficiency and simplify the traditional multi-threaded programming approach, then Node.js maintains an event loop which manages all asynchronous operations for you. When a Node.js application needs to execute an intensive operations (I/O operations, heavy computation), then an asynchronous task is sent to the event loop along with a callback function, and then keep on executing the rest of the application. Then the event loop keeps track the execution status of the asynchronous task, and when it completes the callback is executed/called by returning results to the application.

The event loop efficiently manages a thread pool and optimized the execution of tasks. Leaving this responsibility to the event loop, allows the developers to focus on the application logic for solving the real problem and finally simplifying the asynchronous programming.

Now, let’s go to the practical part of this article. We’re going to install Node.js, and create a MVC framework for starting to develop our Web application.

In order to install Node.js, we need to follow the simple instruction in the official site at https://github.com/joyent/node/wiki/Installation.

Our MVC framework comprises of the following architectural building blocks:

  • a HTTP server for serving HTTP requests. Unlike other development stack where the application server (ex, Tomcat) and web server (ex, Apache) are distinct modules in the solution architecture, in Node.js  not only we implement the application but also we implement the HTTP server
  • a router in order to map HTTP requests to request handlers. This mapping is based on the URL of the request
  • a set of request handlers to fulfill/process the HTTP requests arrived to the server
  • a set of view to present data/forms to the end user
  • a request data binding mechanism in order to understand correctly the data carried onthe incoming HTTP request payload

It’s remarkable to say that we’re going to implement each building block on its underlying module in order to follow/comply the very architectural principles: separation of responsibilities to achieve loosely coupling and high cohesion.

The HTTP server

In this section, we’re going to implement the HTTP server building block of our architecture. Let’s create the server.js module in the root directory of the framework with the following code in Listing 01.

var http = require(“http”);

function start() {

   http.createServer(

      function(request, response) {

         console.log(“HTTP request has arrived”);

         response.writeHead(200, {“Content-Type”: “text/plain”});

         response.write(“Hello World”);

         response.end();

   }).listen(8888);

  console.log(“Server has started at http://localhost:8888/“);

}

exports.start = start;

Listing 01. Module server.js

Let’s analyze the previous code. The first line requires the http module that ships with Node.js and make it accessible through the variable http. This module exposes the createServer function which returns an object, and this object has method listen which receives as input parameter the port is going to listen on. The createServer function receives as input parameter an anonymous function with the logic of the Node.js application. This anonymous function receives as input parameters a request and response objects used to handle the details in the communication with client-side. In this case, we don’t use the request object as well as the response.writeHead sets the HTTP status 200 (OK) and the content type, the response.write sends the content/data to the client-side and finally the response.end closes the communication. Whenever a HTTP request arrived at Node.js, the anonymous function is called (technically, this anonymous function is a callback handler to the on_request events). In this case, whenever a new request arrives, then the message “HTTP request has arrived” is printed on the command line. You may see this message twice, because most modern browsers will try to load the favicon by requesting in this case the following URL http://localhost:8888/favicon.ico.

Finally, the server bootstrapping logic is encapsulated in the start function which is in turned exported by the server.js module.

In next step, let’s create the main module index.js which is used to connect and initialize the remaining modules as shown in the Listing 02. In this case, we’re going to import the server.js internal module and call the start function.

var httpServer = require(“./server”);

httpServer.start();

Listing 02. Module index.js

And finally, let’s run this first part of the application in your terminal as shown in Listing 03.

node index.js

Listing 03

The router and request handlers

Now, we can process a HTTP request and send a response back to the client-side. But, the common in Web application is to do something different depending on the URL associated to the HTTP request. So, we need another abstraction called the router which main feature is to decide which logic to execute based on URL patterns.

Let’s suppose that we’re developing a Web application with three features exposed as /app/feature01, /app/feature02 and /app/feature03.

First of all, let’s create the requestHandlers.js module with the handlers definition as show in the following listing. The handler definition is very simple: we’re just returning a string to display on the browser. If you want to parse the query string or bind the posted form data, the querystring module must be used.

function feature01(request, response) {

        console.log(“HTTP request at /app/feature01 has arrived”);

        response.writeHead(200, {“Content-Type”: “text/plain”});

    response.write(“Feature01 is called”);

    response.end();

}

function feature02(request, response) {

        console.log(“HTTP request at /app/feature02 has arrived”);

   

    response.writeHead(200, {“Content-Type”: “text/plain”});

    response.write(“Feature02 is called”);

    response.end();

}

function feature03(request, response) {

        console.log(“HTTP request at /app/feature03 has arrived”);

   

    response.writeHead(200, {“Content-Type”: “text/plain”});

    response.write(“Feature03 is called”);

    response.end();

}

exports.feature01 = feature01;

exports.feature02 = feature02;

exports.feature03 = feature03;

Listing 04. requestHandlers.js module

Then, let’s create the router.js module with the definition of routing logic as shown in the following listing. In this case, the router receives as input parameters the list of handlers in dictionary form (name == url pattern and value == reference to the underlying request handler), the pathname representing the current url, request and response objects.

function route(handlers, pathname, request, response) {

    if (typeof handlers[pathname] === ‘function’) {

         handlers[pathname](request, response);

    } else {

        response.writeHead(400, {“Content-Type”: “text/plain”});

        response.write(“404 Resource not found”);

        response.end();

    }

}

exports.route = route;

Listing 05. router.js module

Next, we need to refactor the server.js module as shown in the following listing. The url module is required to extract the part of the URL.

var http = require(“http”);

var url = require(“url”);

function start(route, handlers) {

   http.createServer(

      function(request, response) {

         console.log(“HTTP request has arrived”);

        

         var pathname = url.parse(request.url).pathname;

         route(handlers,pathname, request, response);

   }).listen(8888);

  console.log(“Server has started at http://localhost:8888/“);

}

exports.start = start;

Listing 06. Refactoring of server.js module

And finally, let’s refactor the index.js module to register the application URLs and wire the framework modules as shown in the following listing.

var httpServer = require(“./server”);

var requestHandlers = require(“./requestHandlers”);

var router = require(“./router”);

var handlers = {}

handlers[“/app/feature01”] = requestHandlers.feature01;

handlers[“/app/feature02”] = requestHandlers.feature02;

handlers[“/app/feature03”] = requestHandlers.feature03;

httpServer.start(router.route, handlers);

Listing 07. Refactoring module index.js

Now let’s refactor our application to present a Web form and process the input data. Let’s suppose the /app/feature01 is Web form and the /app/feature02 is the endpoint for form processing.

Now let’s create the views.js module with the definition of the Web form related to the /app/feature01 as shown in the following listing.

function feature01View() {

 var html = ‘<html>’+

            ‘<head>’+

            ‘<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″ />’+

            ‘</head>’+

            ‘<body>’+

            ‘<form action=”/app/feature02″ method=”post”>’+

            ‘<input type=”text” name=”data” id=”name”></input>’+

            ‘<input type=”submit” value=”Submit data” />’+

            ‘</form>’+

            ‘</body>’+

            ‘</html>’;

  return html;

}

exports.feature01View = feature01View;

Listing 08. views.js module

Next, we need to refactor the feature01 request handler to send back to the end user the Web form as shown in the following listing.

var views = require(“./views”);

function feature01(request, response) {

        console.log(“HTTP request at /app/feature01 has arrived”);

        response.writeHead(200, {“Content-Type”: “text/html”});

    var html = views.feature01View();

    response.write(html);

    response.end();

}

Listing 09. Refactoring the feature01 request handler function

Now, we have a very interesting way to process POST HTTP request. Because in Node.js the key concept is to process anything in an asynchronous way, then Node.js delivers to our application code the posted data in chunks through callbacks to specific events on Node.js. The data event is called when a new data chunk is ready to be delivered to the application code while the end event is called when all the data chunks are received by Node.js and ready to be delivered to the application code.

Now, we need to refactor the server.js module in order to add logic to select how to process GET or POST methods as shown in the following listing. In the case of the GET method, everything is same as before (no changes). In the case of POST method, we do the following steps:

  1. define that the expected data is encoded using utf8
  2. register a listener callback for the event data to be invoked every time a new chunk of posted data arrives, and then we append every chunk to the resultPostedData variable
  3. register a listener callback for the event end to be invoked when all the posted data is received and ready to be delivered to the underlying request handler passing the  resultPostedData as parameter

var http = require(“http”);

var url = require(“url”);

function start(route, handlers) {

   http.createServer(

      function(request, response) {

         console.log(“HTTP request has arrived”);

         var resultPostedData = “”;

         var pathname = url.parse(request.url).pathname;

         if(request.method==’GET’)

         {

           route(handlers,pathname, request, response);

         }

         else

         if(request.method==’POST’)

         {

           request.setEncoding(“utf8”);

           request.addListener(“data”, function(chunk) {

               resultPostedData += chunk;

           });

           request.addListener(“end”, function() {

               route(handlers, pathname, request, response, resultPostedData);

           });

         }

   }).listen(8888);

  console.log(“Server has started at http://localhost:8888/&#8221;);

}

exports.start = start;

Listing 10. Refactoring of server.js module

We also need to refactor the router.js module as shown in the following listing.

function route(handlers, pathname, request, response, postedData) {

    if (typeof handlers[pathname] === ‘function’) {

         if(postedData === undefined)

        {

           handlers[pathname](request, response);

        }

        else

        {

            handlers[pathname](request, response, postedData);

        }

    } else {

        response.writeHead(400, {“Content-Type”: “text/plain”});

        response.write(“404 Resource not found”);

        response.end();

    }

}

exports.route = route;

Listing 11. Refactoring of the router.js module

And finally, let’s refactor the feature02 request hander as show in the following listing.

function feature02(request, response, postedData) {

        console.log(“HTTP request at /app/feature01 has arrived”);

        response.writeHead(200, {“Content-Type”: “text/html”});

    response.write(“Posted data is”+postedData);

    response.end();

}

Listing 12. Refactoring the feature02 request handler function

And to finish this tutorial, I will explain how to return JSON formatted objects to the client-side. This is  one the brightest features of Node.js because we can build a very scalable and fast back-end on Node.js and later to consume the related services from any client-side technology that understand simple HTTP such as browser, mobile application, etc.

Because we have developed a good framework, then we only need to refactor the feature03 as shown in the following listing.

function feature03(request, response, postedData) {

        console.log(“HTTP request at /app/feature01 has arrived”);

        response.writeHead(200, {“Content-Type”: “application/json”});

    var anObject = { prop01:”value01″, prop02:”value02″}

    response.write(JSON.stringify(anObject));

    response.end();

}

Listing 12. Refactoring the feature02 request handler function

I hope this tutorial on Node.js for dummies is very helpful for you.

I look forward to hearing from you comments anytime you like …

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s