images/nodejs.png

Workshop 24 @ SIGCSE 2014. March 8, 2014. Atlanta, GA. U.S.A.

1. Introduction

Some relevant things you should know about Node.js:

  • Created by Ryan Dahl and debuted in the year 2009.

  • It’s a software platform that can be used to build scalable network (especially server-side) applications.

  • Built on Google Chrome’s V8 JavaScript Engine. It implements ECMAScript as specified in ECMA-262, 5th edition.

  • Achieves high throughput via non-blocking I/O and a single-threaded event loop.

  • Contains a built-in HTTP server library, making it possible to run a web server without the use of external software, such as Apache, and allowing more control of how the web server works.

  • Written in C, C++, and JavaScript.

  • Runs on these operating systems: Windows, Mac OS X, Linux, Solaris, FreeBSD, and OpenBSD.

2. The Node.js Shell

You can run a node shell or REPL (read-eval-print loop) in order to interactively test JavaScript code. At a terminal window type:

node

Now type any JavaScript expression after the shell prompt “>”:

> 1 + 2 * 3
7
> 'hello'.substring(3)
'lo'
> [4, 8, 15, 16, 23, 42].length
6
> process.versions
{ http_parser: '1.0',
  node: '0.10.25',
  v8: '3.14.5.9',
  ares: '1.9.0-DEV',
  uv: '0.10.23',
  zlib: '1.2.3',
  modules: '11',
  openssl: '1.0.1e' }
> console.log('Hello!')
Hello!
undefined

Type .exit followed by Enter to quit the shell or press Ctrl-C twice.

3. A Simple Script

Let’s write our first Node.js program. Type the following code in a text editor and save it as hello.js:

File: hello.js
// A Simple Script

'use strict';                // 1

console.log('Hello Node!');  // 2

This is what’s happening in the above program:

1 The 'use strict' string is a directive introduced in ECMAScript 5. It enables strict mode, which is a restricted subset of the language that fixes a few important language deficiencies and provides stronger error checking and increased security. For example, strict mode makes it impossible to accidentally create global variables.
Important It’s highly advisable to start all your Node.js scripts with the 'use strict' directive.
2 The console.log() function prints its argument to the standard output (typically the terminal window) followed by a newline.

At the terminal, change to the directory where the hello.js file is located and type:

node hello
Note We don’t need to include the .js extension when running the program.
Program output:
Hello Node!

4. A Web Server

Let’s write a more interesting example: a simple web server that responds “Hello Node!” to every request it receives.

Type the following code in your text editor and save it as hello_server.js:

File: hello_server.js
// A Web Server

'use strict';

var http = require('http');                           // 1
var port = 8000;                                      // 2

http.createServer(function (req, res) {               // 3

  console.log(req.method + ' ' + req.url);            // 4
  console.log(req.headers);
  console.log();

  res.writeHead(200, {'Content-Type': 'text/plain'}); // 5
  res.end('Hello Node!\n');                           // 6

}).listen(port);                                      // 7

console.log('Listening at port ' + port);

This is what’s happening:

1 Import the http module.
2 Define the port number from which the server will be accepting connections.
3 Create the web server and send it a callback function. This function takes req and res (HTTP request and response) objects as parameters. Every time a client makes a request, this function will get called.
4 Display to the standard output the request information (method, requested resource, and request headers) followed by a new line.
5 Write the response line and header fields. You need to specify an HTTP status code (200, for example, when the request was successful) and an object with all the response headers.
6 Write the response body.
7 Tell the web server to start accepting connections on the specified port.

To run the server type at the terminal:

node hello_server

Now, from your web browser, visit this following URL: http://localhost:8000/

You should see the “Hello Node!” message.

Web server output:
Listening at port 8000
GET /
{ host: 'localhost:8000',
  'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0)
  Gecko/20100101 Firefox/26.0',
  accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'accept-language': 'en-us,en;q=0.5',
  'accept-encoding': 'gzip, deflate',
  connection: 'keep-alive' }

To stop the server just type Ctrl-C at the terminal.

Important If you get an EADDRINUSE error when starting the server, verify that the port number used by the program is not currently used by any other application running on your system. If you’re having any problems, you can always change the port number. But beware: sometimes this error might happen when you try to run, inadvertently, two instances of the same web server at the same time.

5. A Web Service Client

Meme Generator is graphics generating engine. Users can select from numerous backdrops and characters that are Internet memes and add humorous captions to them. Images from around the web and from users’ personal files can also be uploaded. Resultant creations can be shared by means of a link. The RESTful Meme Generator API exposes the site’s primary functionality, allowing developers to access, resize, and create memes.

In order to access the web services, we will use the request module. In order to install this module, type at the terminal:

npm install request
Note

In Unixish systems (Mac OS X and Linux) you most likely need to prepend the sudo command when running npm, like this:

sudo npm install request

5.1. The Code

This next example shows how to write a Node.js client program that calls one of the web services provided by Meme Generator. It displays to the standard output the twelve most popular “meme generators” of all time.

File: web_service_client.js
// A Web Service Client

'use strict';

var request = require('request');                    // 1

function check(condition, data) {                    // 2
  if (!condition) {
    console.error('Oops! Something bad happended.');
    console.error(data);
    process.exit(1);
  }
}

request('http://version1.api.memegenerator.net/'     // 3
  + 'Generators_Select_ByPopular',
  function (err, r, data) {                          // 4
    check(!err, err);                                // 5
    check(r.statusCode == 200, r.statusCode);        // 6
    var body = JSON.parse(data);                     // 7
    check(body.success, body.success);               // 8
    console.log('Most popular meme generators of the last 10 days:');
    body.result.forEach(function (generator) {       // 9
      console.log('- ' + generator.displayName);     // 10
    });
  });

Let’s review what the program does:

1 Import the request module.
2 Define a function that checks for error conditions. In case we get an error, print the relevant information to the standard error and quit the application.
3 Make a web service request to the Meme Generator server. The specific requested web service is: Generators_Select_ByPopular.
4 Specify the function that will be called when the response is received. It takes three arguments: err (an error object, or null if there was no error), r (the response object), and data (a JSON string containing the body of the response).
5 Check that err is not null.
6 Check that the HTTP response status is equal to 200.
7 Parse the data JSON string into a JavaScript object, store the result in variable body.
8 Check that the web service request was successful.
9 body.result contains an array of generator objects. Each generator object has the following properties: generatorID, displayName, urlName, totalVotesScore, imageUrl, instancesCount, and ranking. We iterate over the array (using its forEach() method) to process each of the generator objects.
10 Print to the standard output each generator’s display name.

Run the program at the terminal:

node web_service_client
Web service client output:
Most popular meme generators of all time:
- Y U No
- Futurama Fry
- Success Kid
- The Most Interesting Man In The World
- willywonka
- Philosoraptor
- one-does-not-simply-a
- First world Problems II
- Forever Alone
- Bad luck Brian meme
- Good Guy Greg
- Scumbag Steve

5.2. Exercises

  1. Review the API documentation to see how to add parameters to a web service request. Modify the web_service_client.js file so that the program receives the most popular meme generators of the last 10 days, with a page size of 5. The output should look similar to this:

    Most popular meme generators of the last 10 days:
    - I Dont Always
    - Bad luck Brian meme
    - Grumpy Cat
    - What If I Told You Meme
    - Dr Evil meme
  2. Now, for each generator, have your program display the following properties: generator ID, URL name, and total votes score. The ID should be placed between square brackets and the score between parenthesis. The output should now look something like this:

    Most popular meme generators of the last 10 days:
    [76] I-Dont-Always (1086)
    [740857] Bad-Luck-Brian-Meme (1990)
    [1771888] Grumpy-Cat (4042)
    [1118843] What-If-I-Told-You-Meme (407)
    [958983] Dr-Evil-Meme (5)

6. Express

Let’s now introduce the Express web framework, which considerably simplifies the way we can build sophisticated web applications on the Node platform.

6.1. Installing Express

We need to use npm to install the express module in your system. At the terminal type:

npm install -g express
Note The -g option installs Express globally, which allows executing the express script from any directory in our system.

The express script can set up an application skeleton for you. Using the generated application is a good way to get started if you’re new to Express, as it sets up an application complete with templates, public assets, configuration, and more.

6.2. Creating a Web App

Let’s create now a web application called sigcse. At the terminal type:

express -e sigcse
Note The -e option adds the Embedded JavaScript (EJS) template support to the application being created. If omitted, it defaults to the jade template engine.

The following directories and files get created:

sigcse/                   1
 ├── app.js
 ├── package.json
 ├── routes/              2
 │    ├── index.js
 │    └── user.js
 ├── views/               3
 │    └── index.ejs
 └── public/              4
      ├── images/
      ├── javascripts/
      └── stlyesheets/
           └── styles.css
1 The sigcse directory is the root of your application. It contains two files: app.js (the auto-generated JavaScript Express application) and package.json (a JSON file that specifies your application’s dependencies).
2 The routes directory holds the server side JavaScript files that contain the callbacks for the various routes in your application.
3 The views directory holds the HTML templates (files with .ejs extension).
4 The public directory contains the static client-side files. For convenience, it includes specific subdirectories for images, JavaScript client code, and CSS style sheets. The public directory may also contain static HTML files.

The next step is to move to the sigcse directory and install any new required dependencies. At the terminal type:

cd sigcse
npm install

A new directory called node_modules is created in this last step. This is where all the application’s required modules get installed.

We can now run the web application. At the terminal type:

node app

Now, point your browser to the following URL: http://localhost:3000/

You should see a “Welcome to Express” message.

6.3. Modifying the Web App

Let’s make our web pages a little more attractive by modifying its style sheet. Open the sigcse/public/stylesheets/style.css file and overwrite it completely with the following code:

File: style.css
body {
    color: #DDD;
    background: #1D1D1D;
    padding: 0 2em;
    margin: 0;
    font-family: sans-serif;
    font-size: 20px;
}

h1 {
    padding: 30px 30px;
    background: #80BD01;
    color: #FFF;
    font-size: 180%;
}

h2 {
    color: #A0C874;
    font-size: 120%;
    margin-top: 40px;
}

li {
    color: #80BD01;
    font-size: 90%;
    font-style: italic;
}

img {
    padding: 5px;
    border: 1px solid #fff;
}

button {
    font-size: 120%;
}

Now, modify the sigcse/routes/index.js file, by adding a few lines of code:

File: index.js
/*
 * GET home page.
 */

'use strict';                                     // 1

exports.index = function(req, res){
  res.render('index', { title: 'Express',
                        message: 'Hi there!',     // 2
                        favoriteThings:
                          ['Raindrops on roses',
                           'Whiskers on kittens',
                           'Bright copper kettles',
                           'Warm woolen mittens',
                           'Brown paper packages tied up with strings'
                          ]
                      }
  );
};
1 We state that we want to use strict mode.
2 We add two new properties to the object that we send as argument to the render() method: message (a string) and favoriteThings (an array of strings).

The values that we placed in the message and favoriteThings properties will be available in the sigcse/views/index.ejs template file. EJS combines data and a template to produce HTML. Let’s edit this file now so that it has the following content:

File: index.ejs
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>                     <!-- 1 -->
    <p><%= message %></p>                              <!-- 1 -->
    <p>These are a few of my favorite things:</p>
    <ul>
      <% favoriteThings.forEach(function (thing) { %>  <!-- 2 -->
      <li><%= thing %>.</li>                           <!-- 1 -->
      <% }); %>                                        <!-- 2 -->
    </ul>
  </body>
</html>
1 JavaScript code between <%= ... %> adds HTML to the result.
2 JavaScript code between <% ... %> is executed.

Restart the application and refresh the browser to see the result: http://localhost:3000/

Important You always need to restart (stop and run again) a web application whenever you change any of its server side .js source files. You usually don’t need to restart the application after modifying any other type of file.

6.4. Calling Web Services from the Web App

This next example is basically a web version of the program in section 5 (the web service client).

Add the following line to the sigcse/app.js file just after the app.get('/users', user.list) instruction:

app.get('/popular', user.popularGenerators);

This line routes the /popular request (using the HTTP GET method) to the user.popularGenerators() function. This function must be defined in the sigcse/routes/user.js file. Let’s edit this file:

File: user.js
/*
 * GET users listing.
 */

'use strict';                                      // 1

exports.list = function(req, res){
  res.send("respond with a resource");
};

var request = require('request');

function check(condition, data) {                  // 2
  if (!condition) {
    throw 'Oops! Something bad happended: '
      + JSON.stringify(data);
  }
}

exports.popularGenerators = function (req, res) {  // 3
  request('http://version1.api.memegenerator.net/' // 4
    + 'Generators_Select_ByPopular',
    function (err, r, data) {
      try {
        check(!err, err);
        check(r.statusCode == 200, r.statusCode);
        var body = JSON.parse(data);
        check(body.success, body.success);
        res.render('popular',                      // 5
                   { title: 'Most Popular Meme Generators',
                     generators: body.result
                   }
        );
      } catch (exception) {                        // 6
        console.error(exception);
        res.send(500, exception);                  // 7
      }
    });
};
1 We state that we want to use strict mode.
2 This function checks possible error conditions. Throws an exception if things are not as they’re supposed to be.
3 Define and export the popularGenerators() function.
4 Do the web service call to the Meme Generator server.
5 Render the result using the popular.ejs template file. Values passed to the template as object properties: title (a string) and generators (an array of generator objects).
6 Error conditions are dealt as exceptions that need to be caught.
7 Respond with a simple error page with a 500 status code (Internal Server Error).

The only thing left is to write the code for the template file. Create a new file called popular.ejs inside the sigcse/views directory:

File: popular.ejs
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <ul>
    <% generators.forEach(function (generator) { %>
      <li><%= generator.displayName %></li>
    <% }); %>
    </ul>
  </body>
</html>

To see the result, restart the web app and point your browser to this URL: http://localhost:3000/popular

6.5. Exercise

Modify the popular.ejs template. For each generator, display all its properties: generatorID, displayName, urlName, totalVotesScore, imageUrl, instancesCount, and ranking. Use an <img> tag and make its src attribute equal to the value of the imageUrl property.

7. Writing Web Services

Writing a web service with Express is fairly easy. Let’s write a web service that returns a pseudorandom adage, reminiscent of the fortune Unix command.

7.1. The Code

First, let’s create the JSON file sigcse/quotes.json with an array of quotes for our web service:

File: quotes.json
[
  { "quote":  "The best way to predict the future is to invent it.",
    "author": "Alan Kay"
  },
  { "quote":  "Adding people to a late software project makes it later.",
    "author": "Fred Brooks"
  },
  { "quote":  "Testing can show the presence of errors, but not their absence.",
    "author": "Edsger Dijkstra"
  },
  { "quote":  "Software is like sex: it's better when it's free.",
    "author": "Linus Torvalds"
  },
  { "quote":  "Before software can be reusable it first has to be usable.",
    "author": "Ralph Johnson"
  },
  { "quote":  "Never trust a computer you can't throw out a window.",
    "author": "Steve Wozniak"
  }
]

Next, add the following line of code to the sigcse/app.js file, just after the
app.get('/popular', user.popularGenerators) instruction:

app.get('/fortune', user.fortune);

With this line we route the /fortune request to the user.fortune() function, which we will define in the sigcse/routes/user.js file. Add at the end of this file the following code:

var fs = require('fs');                                          // 1
var quotes = JSON.parse(fs.readFileSync('quotes.json', 'utf8')); // 2

exports.fortune = function(req, res) {                           // 3
    var rnd = (Math.random() * quotes.length) | 0;               // 4
    var quote = quotes[rnd];
    console.log(quote);
    res.json(quote);                                             // 5
};
1 Import the file system module, in order to read a file.
2 Read and parse the JSON file, put the resulting array in the quotes variable.
3 Define and export the fortune() function.
4 Get a pseudorandom integer number within the index range of the quotes array.
5 Return a JSON response with the corresponding pseudorandom quote.

Restart the web app and test your code pointing your browser to: http://localhost:3000/fortune

Refresh your browser several times and you should probably get different results.

7.2. An AJAX Client

As a final example, let’s write a simple Single Page Application (SPA) using AJAX to consume the web service we just wrote.

Note Although AJAX stands for Asynchronous JavaScript and XML, we will be using JSON instead of XML in order to pass information around.

Create a new file called fortune.html and place it in the sigcse/public/ directory with the following content:

File: fortune.html
<!DOCTYPE html>
<html>
  <head>
    <title>Fortune</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <script
      src='http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'>
    </script>                                                   <!-- 1 -->
    <script>
      $(function () {                                             // 2
        $('#get_fortune').click(function () {                     // 3
          $.getJSON('/fortune', function (data) {                 // 4
            $('#quote_info').html(                                // 5
              '<p><em>' + data.quote + '</em></p>'
              + '<p>&mdash; ' + data.author + '</p>');
          });
        });
      });
    </script>
  </head>
  <body>
    <h1>Fortune</h1>
    <button id='get_fortune'>Get Fortune</button>               <!-- 6 -->
    <div id="quote_info"></div>
  </body>
</html>
1 Reference the jQuery library using a <script> tag.
2 Define the callback function that will run once the document is ready. This is to prevent any jQuery code from running before the document has finished loading.
3 Assign a click event to the tag with the get_fortune ID.
4 Make the AJAX (using JSON) call to the /fortune web service, and set its callback function.
5 Use the result of the AJAX call to update the HTML body of the tag with the quote_info ID.
6 Define the HTML elements with the corresponding id attributes.

Point your browser to this URL: http://localhost:3000/fortune.html

Press the “Get Fortune” button several times to see the result.

8. Additional Resources

8.1. Web Sites

8.2. Books

Shelley Powers.
Learning Node.
O’Reilly Media, 2012.
ISBN-13: 9781449323073.

images/powers.png

Mike Cantelon, Marc Harter, TJ Holowaychuk, Nathan Rajlich.
Node.js in Action.
Manning Publications Company, 2013.
ISBN-13: 9781617290572.

images/cantelon.png