CHAPTER 7

image

Real-time Collaboration with Node.js

Throughout the book so far, all the code we have written has been client side, but although client side JavaScript is very powerful nowadays it is often necessary to have a server so that data can be stored online, to keep sensitive logic out of the client, or so that users can interact with each other online. You may be familiar with other server side languages such as PHP or Ruby (possibly even Perl!), but one of the advantages of Node.js is that you write server side code in JavaScript so it should be easy to pick up because you have client side JS experience.

Making a Chatroom

In this chapter we use Node.js with Socket.IO to create a real time chatroom web app that allows multiple users to talk to each other. I guide you through setting up Node.js on your local machine because most commonly used servers (shared hosting) do not support Node.js, but do be aware that some services such as Heroku and Amazon Web Services do have free tiers.

Installing Node.js

Obviously to write anything using Node.js, you need to have it installed. There are many ways to install it. You can use a package manager such as apt-get, yum, or Homebrew (depending on which operating system you are using) but these are not always up to date. On the Node.js website, www.nodejs.org, you can download the latest version executable binaries or build it from source code. Alternatively, you can use the Node Version Manager (nvm) from www.github.com/creationix/nvm, which is particularly useful if you are working with many codebases that are not all using the latest version. Node comes with a built-in package manager called npm, which handles dependencies using a file called package.json, so rather than installing Express and Socket.IO yourself, you just put it in the file. When you are ready to install the packages, run npm install within the folder that has package.json.

{
  "name": "javascript-creativity-chatroom",
  "description": "A chatroom made in Chapter 7 of the JavaScript Creativity book.",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "express": "3.x",
    "socket.io" : "0.9.x"
  }
}

Unlike traditional JavaScript, Node.js has a module system that allows you to load JavaScript files at runtime. Each module is defined in a file, exporting variables and functions through a module.exports object. In another file, you can use the 'require' function to load the module and get to its module.exports contents. There are three different ways to load a module.

  • require() a relative file, for example require('./lib/utils.js'). This requires the single file and returns the value of 'module.exports' in that file. You’ll use this method quite a lot if you have large, multi-file projects.
  • require() a core module, which are special keywords, for example require('http') to load the core http module. Node.js comes installed with a host of standard core modules, but they are not included by default, meaning you have to require them when you want them. You can see the full list of core modules in the Node.js documentation (www.nodejs.org/api/index.html).
  • require() a distinct module from a node_modules folder, for example require('express'). This is the most powerful feature of the module system. You can require entire separate modules by referencing the module name. The way this works is that Node.js has special knowledge of the node_modules folder. It walks up the directory tree from the current script, looking for node_modules/<name>/package.json or node_modules/<name>/index.js. It looks at the package.json first, is to see whether it has a “main” property, which references a .js file to load that is named differently to index.js. Let’s say you have a file /www/myapp/index.js, in this file you have require('express'). Node.js looks for the module in the following directories:
  • /www/myapp/node_modules/express/(package.json or index.js)
  • /www/node_modules/express/(package.json or index.js)
  • /node_modules/express/(package.json or index.js)
  • $NODE_PATH/express/(package.json or index.js)
  • $HOME/.node_modules/express/(package.json or index.js)
  • $HOME/.node_libraries/express/(package.json or index.js)
  • $PREFIX/lib/node/express/(package.json or index.js)
  • This is an exhaustive list of the folders Node.js looks in; it is highly recommended that you ensure your modules are placed in the local node_modules folder, for speed and reliability. The other folders are mostly useful for when Node.js modules inside a node_modules folder require their own modules, so you can share modules between your project and your modules.

This is where npm steps in to make things even better. Due to the features of node_modules folders in Node.js, npm looks at the “dependencies” and “devDependencies” objects in your package.json, and download each to your node_modules folder. It also downloads all your modules’ dependencies, and your modules’ modules’ dependencies, and so on. The difference between “dependencies” and “devDependencies” is that npm will not install devDependencies of other modules, only dependencies. By default npm installs everything into the local node_modules folder. This isn’t always what you want, so if you install modules with npm install -g <module>, then it installs them globally (typically in $HOME/.node_modules/). This is really useful as you can use this to install command line programs written in Node.js. For example I use nodemon by Remy Sharp as an alternative way to run the node server while I am developing a project, because the default node [filename] approach to running a server does not watch for files that have been updated while the server is running. To install nodemon you run npm –g install nodemon, then run nodemon [filename] to run a server through nodemon. More information about nodemon can be found at www.remy.github.io/nodemon/.

The simplest example of Node.js is, of course, Hello World. To serve a web page we need to require a module called http, which lets us handle the low-level aspects of HTTP. This module is a core module, so we use the first definition in the list. Once HTTP is required, we store it within a variable called http. We then use a method on http called createServer; this has a callback that lets you send a response (also known as the web page). To create the web page that gets served, we start by writing the headers using response.writeHead. The first parameter that needs to be provided is the HTTP status code 200 (OK). The second is the actual header, in this example we are only writing “Hello World” so the Content-Type can be text/plain, you may choose to use text/html if you want to write some HTML to the page. To write the actual content, you just use response.write. Once you’ve finished writing the response, end it with response.end(). Now you have a server but to actually serve the page you need to tell it to listen for activity on a port (in this case, port 8080).

var http = require("http");
http.createServer(function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}).listen(8080);

Now that you know how to require a module and do something with it, let’s make our own simple module that makes use of the module.exports that I mentioned. This module will wrap up our Hello World code into a file called helloworld.js, which we then require as a variable called hello in server.js and call the server method.

helloworld.js

var http = require("http");
 
function hello() {
  http.createServer(function(request, response) {
    response.writeHead(200, {"Content-Type": "text/html"});
    response.write("Hello World");
    response.end();
  }).listen(8080);
}
 
module.exports.server = hello;

server.js

var hello = require('./helloworld.js'),
hello.server();

Of course, this is an incredibly simplistic example but it shows you quite nicely how you can require a custom module and call a method based on the name that it is exported as rather than the name of the function. It may help to point out here the differences between a method and a function, because many people use them interchangeably (which is fine, but I prefer to be more specific). A method is used on an object, while a function is independent of any object.

Socket.IO and Express

I mentioned Socket.IO earlier, it was one of the packages we installed from package.json, but I did not explain what it does. Basically it is a wrapper for web sockets so that all browsers can support real-time communication.

Socket.IO aims to make real-time apps possible in every browser and mobile device, blurring the differences between the different transport mechanisms. It’s care-free real-time 100% in JavaScript.

Socket.IO homepage

Socket.IO is also incredibly easy to use. Once it is installed, it needs to be required within the server-side files (similar to requiring a file in PHP, but node automatically finds the location that it is installed) and then set it to listen to the server.

Of course, we have not yet made a server. Languages such as PHP execute once per file and translate the file’s contents into an output, Node.js is different in that it is a long-standing process that will serve many files many times. Node.js includes an HTTP module, although you can use apache or nginx if you want, that gives you the capability of creating a simple server yourself—but for most situations you don’t want to be mucking around with low-level server code.

Express.js makes it easy to get to the actual code-writing part of developing a website, rather than worrying about how each file can access the rest of the file structure and so on. However, it isn’t very opinionated; with Express.js you can use any paradigm (or way of working) that you like. You can write simple websites, blogs, advanced web apps, or a REST API using the tools that Express provides. If you do prefer an opinionated approach, there is an official structuring tool for Express called express-generator that can be installed globally using npm to help you get started on a new project, although it doesn’t force you to keep using the same structure.

As a brief example of using Express to manage the low-level code, see the two code snippets that follow. They both write ‘Hello World!’ to the page if you are on localhost:8080/hello, and they write ‘Goodbye cruel world!’ if you are on any other page. The difference is that the first snippet directly uses the http and the second snippet uses Express. Of course this barely scratches the skin of what Express is capable of, but it should show you nicely how Express does not require the lower-level code such as HTTP headers.

Without Express

var http = require('http'),
http.createServer(function(request, response) {
  var url = request.url;
  response.writeHead(200, {"Content-Type": "text/html"});
  if (url == '/hello')  {
    response.write("Hello World!");
  }
  else  {
    response.write("Goodbye cruel world!");
  }
  response.end();
}).listen(8080);

With Express

var express = require('express'),
var app = express();
app.get('/hello', function(request, response) {
  response.send("Hello World!");
});
app.get('/*', function(request, response) {
  response.send("Goodbye cruel world!");
});
app.listen(8080);

Now let’s use Express to begin the writing the server for our chatroom. At the beginning of the chapter there was a package.json for installing Express and Socket.IO, if you haven’t yet installed those packages, then you will need to do so now. Using Socket.IO is slightly different than using Express on its own in that it requires an http.createServer instance to be passed to Socket.IO’s listener. This was changed in version 3 of Express, prior to that the express() function returned an instance of http.Server.

var app = require('express')(),
    server = require('http').createServer(app),
    io = require('socket.io').listen(server);
server.listen(8080);

Currently there is nothing to see, because we didn’t even write ‘Hello World’ to the page this time, so let’s set up a basic page using just HTML. For the most basic chatroom, all we need is two text boxes to put your name and message in (you could do name differently depending on UI choices) as well as a button to send the message and a div (you could use a list to improve semantics) to put the messages in. This file can be (although doesn’t have to be) saved as index.html in the same folder as the server code, as you would normally.

<!doctype html>
<html>
    <head>
        <title>Chatroom</title>
    </head>
    <body>
        <label for="nickname">Nickname:</label><input type="text" id="nickname"></input>
        <div id="messages"></div>
        <textarea id="message"></textarea>
        <a id="send">Send</a>
    </body>
</html>

Now you need to use routers (as we did in the Hello World example) to tell the browser where to find content. For this chatroom, we want multiple rooms to be accessible and a nice way to do this is have the name of the chatroom (or a random identifier) in the URL of the room. Express has a way to easily check for this name, as a pathname segment (also known as a named parameter), but because this chatroom does not need multiple views, we can just direct every page to index.html. We could use a wildcard for this instead of a pathname segment, but it is a nice way to introduce the idea of pathnames being used as parameters. It is also a way to force a chatroom id to be included, as it will not serve the page without any id; this means that a separate route would be needed if you want to add a homepage.

app.get('/:id?', function (req, res) {
    res.sendfile(__dirname + '/index.html'),
});

Of course, this is a rather useless chatroom. Let’s write the server-side code for dealing with messages. Using Socket.IO we need to check for a connection, using io.sockets.on('connection', function (socket) {}); as an event handler for any connections, then join the room that is named in the pathname segment. Unlike Express, to get the identifier we use var room = socket.handshake.headers.referer as the Express .get call is not available once it has already been routed. Other ways to get the identifier would be to send the pathname segment from the client side or to initially serve it using a res.local (variables scoped to the request/response cycle) and exposing as a global variable on the client side. Using socket.handshake is quite long but in this case it is a nice way to avoid sending from the client side.

With the identifier stored in a variable called room, we can now use socket.join(room); to join a Socket.IO room with the same name as the ID. These rooms are basically a way to send the right messages to the right people, only members of the room will get the messages for that particular room.

To check for messages or when the user leaves the room, we use event handlers on socket. Every communication over Socket.IO is classed as a message, not to be confused with the messages that are written in the chatroom. Sending messages is called emitting. For messages in the chatroom to be sent to each other, we first emit the message on the client side (which I will get to shortly) to the server, which then sends it to the other clients connected to the room. To emit a message to the room from the server we use io.sockets.in(room).emit(key, value);. For a chatroom message we use the string 'message' as the key and the data is passed through as the value from a data parameter on the event listener. For leaving a room, the key is 'leave'.

io.sockets.on('connection', function (socket) {
    var room = socket.handshake.headers.referer;
    socket.join(room);
    socket.on('message', function (data) {
        io.sockets.in(room).emit('message', data);
    });
    socket.on('leave', function (room) {
        socket.leave(room);
    });
});

And that’s it for the server in terms of a basic chatroom, you can see how brief the code is in Listing 7-1. All we need to do now is for the event handlers on the client-side to emit the messages when sent so that, using the power of Socket.IO rooms and the code we have just written, it will automatically send the messages to anybody in the same room— in real time! There is no AJAX or long polling to worry about, Socket.IO uses the best method available (most modern browsers use Web Sockets but others fall back to the appropriate method). The lack of using AJAX is one of the biggest factors in my choice to not use jQuery or any similar libraries for this chapter, though you may need to for larger projects.

Listing 7-1.

<script>
    var socket = io.connect('http://localhost'),
 
    socket.on('message', function(evt) {
        document.querySelector("#messages").innerHTML += '<p><span class="nickname">'+evt.nickname+': </span><span class="message">' + evt.message +'</span></p>';
    });
 
    sendButton = document.querySelector("#send");
    var messages = document.querySelector("#message");
    sendButton.addEventListener('click', function() {
 
        socket.emit('message', {
          "message": messages.value,
          "nickname": document.querySelector("#nickname").value || 'Guest'
        });
 
        messages.value = "";
    });
</script>

image Note  To use Socket.IO on the client-side you must include <script src="/socket.io/socket.io.js"></script> although you do not need to worry about the location because Node.js deals with it automatically.

Summary

Many Node.js beginners always feel a bit daunted at the prospect of learning a technology that is so different from what they are used to. I hope this chapter has eased you in to the world of Node.js in such a way that you find it almost laughably easy to make a real-time multi-user application. Chatrooms can be very diverse, so this exercise should give you a great starting point to learn more of the language. You can take it further by enhancing your chatroom using features of the language that you find interesting. In the next chapter we will be adding video and audio streaming to the chatroom as a conference-style feature that will take you to the next level of node expertise.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.28.108