I'm a web developer in Norfolk. This is my blog...

Building a Chat Server With Node.js and Redis

One of the more interesting capabilities Redis offers is its support for Pub/Sub. This allows you to subscribe to a specific channel, and then react when some content is published to that channel. In this tutorial, we’ll build a very simple web-based chat system that demonstrates Redis’s Pub/Sub support in action. Chat systems are pretty much synonymous with Node.js - it’s widely considered the “Hello, World!” of Node.js. Since we already used Node with the prior Redis tutorial, then it also makes sense to stick with it for this project too.

Installing Node.js

Since the last tutorial, I’ve discovered NVM, and if you’re using any flavour of Unix, I highly recommend using it. It’s not an option if you’re using Windows, however Redis doesn’t officially support Windows anyway, so if you want to follow along on a Windows machine I’d recommend using a VM.

If you followed the URL shortener tutorial, you should already have everything you need, though I’d still recommend switching to NVM as it’s very convenient. We’ll be using Grunt again, so you’ll need to make sure you have grunt-cli installed with the following command:

$ npm install -g grunt-cli

This assumes you used NVM to install Node - if it’s installed globally, you may need to use sudo.

Installing dependencies

As usual with a Node.js project, our first step is to create our package.json file:

$ npm init

Answer the questions so you end up with something like this (or just paste this into package.json and amend it as you see fit):

Now, if you followed on with the URL shortener tutorial, you’ll notice that we aren’t using Jade - instead we’re going to use Handlebars. Jade is quite a nice templating system, but I find it gets in the way for larger projects - you spend too much time looking up the syntax for things you already know in HTML. Handlebars is closer to HTML so we will use that. We’ll also use Socket.IO extensively on this project.

Support files

As before, we’ll also use Mocha for our unit tests and Istanbul to generate coverage stats. We’ll need a Grunt configuration for that, so here it is:

If you compare this to the code for the URL shortener, you’ll notice a few fairly substantial differences. For one thing, we set up two Redis connections, not one - that’s because we need to do so when using Pub/Sub with Redis. You’ll also notice that we register Handlebars (hbs) rather than Jade, and define not just a directory for views, but another directory inside it for partials. Finally, setting it up to listen at the end is a bit more involved because we’ll be using Socket.IO.

Now, you can run your tests again at this point, but they won’t pass because we haven’t created our views. So let’s do that. Create the directory views and the subdirectory partials inside it. Then add the following content to views/index.hbs:

Don’t worry about the coveralls task failing, as that only needs to pass when it runs on Travis CI.

So we now have our main route in place. The next step is to actually implement the chat functionality. Add this code to the test file:

// Test sending a message

describe('Test sending a message', function () {

it("should return 'Message received'", function (done) {

// Connect to server

var socket = io.connect('http://localhost:5000', {

'reconnection delay' : 0,

'reopen delay' : 0,

'force new connection' : true

});

// Handle the message being received

socket.on('message', function (data) {

expect(data).to.include('Message received');

socket.disconnect();

done();

});

// Send the message

socket.emit('send', { message: 'Message received' });

});

});

This code should be fairly straightforward to understand. First, we connect to the server. Then, we set up a handler to verify the content of the message when it gets sent. Finally, we send the message. Let’s run the tests to make sure we get the expected result:

$ grunt test

Running "jshint:all" (jshint) task

>> 2 files lint free.

Running "mocha_istanbul:coverage" (mocha_istanbul) task

Listening on port 5000

server

Starting the server

Test the index route

✓ should return a page with the title Babblr (337ms)

Test sending a message

1) should return'Message received'

Stopping the server

1 passing (2s)

1 failing

1) server Test sending a message should return'Message received':

Error: timeout of 2000ms exceeded

at null.<anonymous> (/Users/matthewdaly/Projects/babblr/node_modules/mocha/lib/runnable.js:159:19)

Now, let’s implement this functionality. Add this at the end of index.js:

// Handle new messages

io.sockets.on('connection', function (socket) {

// Subscribe to the Redis channel

subscribe.subscribe('ChatChannel');

// Handle incoming messages

socket.on('send', function (data) {

// Publish it

client.publish('ChatChannel', data.message);

});

// Handle receiving messages

var callback = function (channel, data) {

socket.emit('message', data);

};

subscribe.on('message', callback);

// Handle disconnect

socket.on('disconnect', function () {

subscribe.removeListener('message', callback);

});

});

We’ll go through this. First, we create a callback for when a new connection is received. Inside the callback, we then subscribe to a Pub/Sub channel in Redis called ChatChannel.

Then, we define another callback so that on a send event from Socket.IO, we get the message and publish it to ChatChannel. After that, we define another callback to handle receiving messages, and set it to run when a new message is published to ChatChannel. Finally, we set up a callback to handle removing the listener when a user disconnects.

Note the two different connections to Redis - client and subscribe. As mentioned earlier, you need to use two connections to Redis when using Pub/Sub. This is because a client subscribed to one or more channels should not issue commands, so we use subscribe as a dedicated connection to handle subscriptions, and use client to publish new messages.

We’ll also need a bit of client-side JavaScript to handle sending and receiving messages. Amend main.js as follows:

$(document).ready(function () {

'use strict';

// Set up the connection

var field, socket, output;

socket = io.connect(window.location.href);

// Get a reference to the input

field = $('textarea#message');

// Get a reference to the output

output = $('div.conversation');

// Handle message submit

$('a#submitbutton').on('click', function () {

// Create the message

var msg;

msg = field.val();

socket.emit('send', { message: msg });

field.val('');

});

// Handle incoming messages

socket.on('message', function (data) {

// Insert the message

output.append('<p>Anonymous Coward : ' + data + '</p>');

});

});

Here we have one callback that handles sending messages, and another that handles receiving messages. Note that every message will be preceded with Anonymous Coward - we won’t implement user names at this point (though I plan it for a future instalment).

Then visit http://localhost:5000, you should be able to create new messages. If you then open it up in a second tab, you can see messages added in one tab appear in another. Deploying to Heroku using Redis To Go will be straightforward, and you can then access it from multiple devices and see new chat messages appear in real time.

Wrapping up

This illustrates just how straightforward it is to use Redis’s Pub/Sub capability. The chat system is still quite limited, so in a future instalment we’ll develop it further. You can get the source code from the Github repository - just switch to the lesson-1 tag.

About me

I'm a web and mobile app developer based in Norfolk. My skillset includes Python, PHP and Javascript, and I have extensive experience working with CodeIgniter, Laravel, Django, Phonegap and Angular.js.