Create an HTML5 chat app on Bluemix with Node.js, Redis, and Socket.io

22 July 2014
PDF (291 KB)
 

Sign up for IBM Bluemix
This cloud platform is stocked with free services, runtimes, and infrastructure to help you quickly build and deploy your next mobile or web application.

Until recently, creating a web-based chat app was not a simple project. The HTTP protocol makes web-based chat difficult to implement because its request-response architecture does not lend itself well to the constantly updating nature of chat. This article shows that this has all changed, thanks to HTML5 and the WebSocket API. You'll learn how to use the Socket.io library to create a real-time chat app in no time at all.

The app runs on Bluemix, a platform that makes it a breeze to push your apps out to the world.

On the server side, the app described in this article is powered by Node.js, the popular JavaScript server-side runtime powered by the V8 JavaScript engine. It serves regular HTTP requests using the Express framework and the Jade template engine. Most of the heavy lifting for this app is handled by Socket.io, but it's easy to implement on both the server and the client. The app also caches the 100 most recent messages in a Redis data store. The app runs on Bluemix, a platform that makes it a breeze to push your apps out to the world.

What you'll need to build your app

 
  • A basic familiarity with HTML, CSS, and JavaScript
  • Node.js
  • Redis
  • A Bluemix account
  • The Bluemix command-line interface (cf)

Note: The complete source code for the application is available from IBM DevOps Services. For brevity, the article does not include full source code listings. Download the source code (click Get the code above) so you can follow along.

Step 1. Create the app

 

Create a directory for the app on your computer and add the package.json file.

    {
      "name": "bluemixchat",
      "version": "0.0.1",
      "private": true,
      "scripts": {
        "start": "node server.js"
      },
      "dependencies": {
      }
}

Install the required npm modules:

  • Express
  • Jade
  • Redis
  • Socket.io

Install these modules using npm install --save express jade redis socket.io.

This command installs the npm modules (and any other modules that they are dependent on) into your project's node_modules subdirectory, and the command automatically adds these modules as dependencies in your package.json file. As shown below, the next step is to get a bare-bones Express app (server.js) up and running.

    // Startup Express App
    var express = require('express');
    var app = express();
    app.listen(process.env.PORT || 3000);

    // Configure Jade template engine
    var path = require('path');
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'jade');
    app.use(express.static(path.join(__dirname, 'public')));

    // handle HTTP GET request to the "/" URL
    app.get('/', function(req, res) {
      res.render('index');
    });

In the previous code listing, the app handles incoming requests for the / URL and renders a view named index. Create that view now. First, create a subdirectory named views, and in it, add the file index.jade. The contents of this file are shown in the following code listing.

Note: This code loads a stylesheet to style the app from the public/stylesheets subdirectory. This stylesheet file is not covered in the article. The stylesheet file that contains the CSS code is in the code you downloaded from DevOps Services.

    doctype html
    html(lang='en')
      head
        meta(charset='utf-8')
        title Bluemix Chat
        meta(name='viewport', content='initial-scale=1.0, width=device-width, \
         height=device-height, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no')
        link(rel='stylesheet', href='//cdn.jsdelivr.net/normalize/3.0.1/normalize.min.css')
        link(rel='stylesheet', href='/stylesheets/style.css')    
      body
        h1 Bluemix Chat
        form
          input(id='msg', autocomplete='off', autofocus)
          button(type='submit') Send
        ul#messages

The Jade template engine offers a shorthand way of defining HTML pages. Note that it uses white space to determine the structure of the document. Make sure you don't mix spaces and tabs, and be very careful with how you indent your code.

You should now be able to run your app. At the command line in your project directory, run npm start.

This command runs a web server on your machine on port 3000. To see the app in action, point your browser to http://localhost:3000. You should see a window similar to the following image.

Screen capture shows shell Bluemix chat

Step 2. Push the app to Bluemix

 

To deploy the app to Bluemix, use the command-line interface tool, cf. If you don't have this, go to the project's GitHub page for information on how to install it. Alternatively, you can create apps and services from the Bluemix dashboard, but you need the cf tool to deploy your app; therefore, I use it to do everything in this article.

To connect to the Bluemix API and log in using your Bluemix account, run the following commands:

    cf api https://api.bluemix.net
    cf login

Provide your email address and password after you enter the second command.

Warning: Enter unique names and host names for your application when you deploy it to Bluemix. If you use the names provided in these examples, you get errors.

You can now deploy this on Bluemix using cf push bluemixchat.

When the app has deployed, a message indicates the app is started and gives the URL to the app. To test that the app is working, point your browser to http://bluemixchat.mybluemix.net. The next step is to enable real-time messaging using Socket.io.

Step 3. Update clients in real-time using Socket.io

 

Socket.io is an npm module that makes it really simple to build applications that use HTML5 WebSockets on the server and the client. When you bind Socket.io to your Node.js app, it automatically serves a client-side JavaScript library at the URL /socket.io/socket.io.js you can use to push and listen for new messages from the server.

To add the Socket.io server to the Express app, find the following line in the server.js file: app.listen(process.env.PORT || 3000);. Replace this line with the following code.

    var http = require('http').Server(app);
    var io = require('socket.io')(http);
    http.listen(process.env.PORT || 3000);

This code loads the HTTP module and binds the Socket.io server to it. The code listens for incoming requests on port 3000 or the port defined in the PORT environment variable for the current process. When the app is deployed to Bluemix, the PORT setting defines what port to listen to.

Next, add some code to handle the various events in the chat app and to send messages to connected clients using Socket.io. At the end of the server.js file, add the following code.

    // socket.io listen for messages
    io.on('connection', function(socket) {  
      // When a message is received, broadcast it
      // to all users except the originating client
      socket.on('msg', function(data) {        
        socket.broadcast.emit('msg', data);        
      });

      // When a user joins the chat, send a notice
      // to all users except the originating client
      socket.on('join', function(nickname) {
        // Attach the user's nickname to the socket
        socket.nickname = nickname;
        socket.broadcast.emit('notice', nickname + ' has joined the chat.');
      });

      // When a user disconnects, send a notice
      // to all users except the originating client
      socket.on('disconnect', function() {
        socket.broadcast.emit('notice', socket.nickname + ' has left the chat.');
      });
    });

Now that the server-side code is complete, add code on the client-side to interact with Socket.io. Add the following code to the end of the file views/index.jade. Note that this code should be indented within the body element in the Jade template. If it's not, you might experience problems.

    script(src='//cdn.jsdelivr.net/jquery/2.1.1/jquery.min.js')
    script(src='/socket.io/socket.io.js')
    script(src='/javascripts/client.js')

This code loads jQuery from a Content Delivery Network (CDN), Socket.io from the standard location the server makes it available from, and a client.js file to be stored in the public/javascripts directory in your app. Create this file now. Its contents are shown below.

    $(document).ready(function() {
      var socket = io(), nickname, msgList = $('#messages');

      // Check if nickname stored in localStorage
      if('localStorage' in window && localStorage.getItem('nickname')) {
        nickname = localStorage.getItem('nickname');
      } else {
        // If not in localStorage, prompt user for nickname
        nickname = prompt('Please enter your nickname');
        if('localStorage' in window) {
          localStorage.setItem('nickname', nickname);
        }
      }  

      // Send message to server that user has joined
      socket.emit('join', nickname);

      // Function to add a message to the page
      var newMessage = function(data) {
        var who = $('<div class="who">').text(data.nickname),
            when = $('<div class="when">').text(new Date().toString().substr(0, 24)),
            msg = $('<div class="msg">').text(data.msg),
            header = $('<div class="header clearfix">').append(who).append(when),
            li = $('<li>').append(header).append(msg);    

        msgList.prepend(li);
      };

      // Handle the form to submit a new message
      $('form').submit(function(e) {
        var msgField = $('#msg'),        
            data = { msg: msgField.val(), nickname: nickname, when: new Date() };

        e.preventDefault();
        // Send message to Socket.io server
        socket.emit('msg', data);
        // Add message to the page
        newMessage(data);
        // Clear the message field
        msgField.val('');    
      });  

      // When a message is received from the server
      // add it to the page using newMessage()
      socket.on('msg', function(data) { newMessage(data); });

      // When a notice is received from the server
      // (user joins or disconnects), add it to the page
      socket.on('notice', function(msg) {
        msgList.prepend($('<div class="notice">').text(msg));
      });
    });

This code checks whether the user has already entered a nickname by looking up the browser's localStorage store. If not, the code prompt the user to enter one. The code stores the nickname in localStorage for future use. When the page loads, a message is sent to Socket.io to specify that a new user has joined. When the message form is submitted, the app adds the message to the page and sends it to the server. In the last step, the app listens for two Socket.io events, msg and notice, and updates the page any time it receives data on these events.

Run the app again to enter messages and see them displayed on the screen. You can launch another browser and connect to the app to send messages on one browser and see them on the other. Try it with a few browsers or devices — you've just built a functional chat app.

Next, learn how to store these messages using a Redis data store.

Step 4. Store message history in Redis

 

When you use the app as it stands, notice that when you refresh the page, all the chat history is lost. If you're using the app on a mobile device, this creates an additional problem because mobile users tend to use apps such as this intermittently. When their device locks and they reopen the browser, it might refresh the page, which causes the history to be lost.

To resolve this, store the chat history in a data store. The Redis store makes it a breeze to store data such as key-value pairs, lists, etc. It is much faster than a regular database because it stores data in memory rather than on disk. It is perfect for caching data, which makes it a great fit for storing the message history in the chat app.

Make sure that Redis is running on your local machine. (Typically, this is done by running the command redis-server). Next, find the following line in the file server.js: http.listen(process.env.PORT || 3000);. Insert the following code below this line.

    // Configure Redis client connection
    var redis = require('redis');
    var credentials;
    // Check if we are in Bluemix or localhost
    if(process.env.VCAP_SERVICES) {
      // On Bluemix read connection settings from
      // VCAP_SERVICES environment variable
      var env = JSON.parse(process.env.VCAP_SERVICES);
      credentials = env['redis-2.6'][0]['credentials'];
    } else {
      // On localhost just hardcode the connection details
      credentials = { "host": "127.0.0.1", "port": 6379 }
    }
    // Connect to Redis
    var redisClient = redis.createClient(credentials.port, credentials.host);
    if('password' in credentials) {
      // On Bluemix we need to authenticate against Redis
      redisClient.auth(credentials.password);
    }

Note: When you deploy the app to Bluemix later, it will read the connection settings for Redis from the VCAP_SERVICES environment variable. This code connects and authenticates against the Redis server.

Update the server app to push new messages to Redis when they are received. Find the following block:

    socket.on('msg', function(data) {        
      socket.broadcast.emit('msg', data);        
    });

At the start of the anonymous function, add the following lines.

    redisClient.lpush('messages', JSON.stringify(data));
    redisClient.ltrim('messages', 0, 99);

This code pushes the latest message to the messages list in Redis, and it then trims the list to the 100 most recent items (if it has more than 100 items). The final piece of the puzzle is to look for existing messages in Redis when the client launches. To do this, find the following block:

    app.get('/', function(req, res) {
      res.render('index');
    });

Replace this block with the following code.

    app.get('/', function(req, res) {
      // Get the 100 most recent messages from Redis
      var messages = redisClient.lrange('messages', 0, 99, function(err, reply) {
        if(!err) {        
          var result = [];
          // Loop through the list, parsing each item into an object
          for(var msg in reply) result.push(JSON.parse(reply[msg]));
          // Pass the message list to the view
          res.render('index', { messages: result });    
        } else res.render('index');
      });    
    });

This code gets the 100 most recent items in the Redis list messages, loops through them, and parses them as JSON into JavaScript objects. It then passes an array of these objects to the view. Open the file views/index.jade and nest the following code within the ul#messages element.

    - for(var i=0,ln=messages.length;i<ln;i++)      
      li
        .header.clearfix
          .who= messages[i].nickname
          .when= new Date(messages[i].when).toString().substr(0, 24)
        .msg= messages[i].msg

Launch the app again and send some messages. Notice that when you reload the page, the message history is restored. The app doesn't currently store user join and disconnect notices, but you can easily extend it to do this. As a last step, get Redis set up on Bluemix and push the final product out to the world.

Step 5. Create and bind to a Redis service on Bluemix

 

To push the latest changes to Bluemix, provision a Redis service instance and bind it to your app. To create the service, use cf create-service redis 100 bmcredis.

Change the database name (bmcredis in the previous command) to something unique. For the app to be able to connect to the database, you need to bind it: cf bind-service bluemixchat bmcredis.

Restart your app for the binding to become active. Push your changes to Bluemix using cf push.

Congratulations! You have now built a chat application using Node.js, Redis, and Socket.io, and you have deployed it to Bluemix. You can see that the app is up and running.

Screen captures show Bluemix chat in action

Conclusion

 

The app you have built is a fully functional chat app, but there are many ways you can extend it. You can enable users to change their nicknames or to see a list of other connected users. You can add support for multiple rooms, private messaging, and much more. Because you've deployed this app on Bluemix, any features you add can be deployed with a simple cf push command. It doesn't get much easier than that.


RELATED TOPICS:Node.jsRedis

Add a comment

Note: HTML elements are not supported within comments.


1000 characters left

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Cloud computing
ArticleID=978352
ArticleTitle=Create an HTML5 chat app on Bluemix with Node.js, Redis, and Socket.io
publish-date=07222014