Building a Node.js Chat Application and
Sharing Socket.IO Across Multiple Subdomains

I ran into an interesting problem a few weeks ago when trying to use Socket.IO on two separate subdomains with Express.js.

The issue was that events were not being dispatched correctly to my Socket.IO instance if the application was running through a subdomain or virtual host using Express.js.

The workaround I found is to just create one Socket.IO instance in your root level app and then allow your subdomain applications to access that instance via a global variable.

In this example I’ll show you how to setup a simple chat server in Node.js and Socket.IO and then run multiple autonomous instances of that chat server each on their own subdomain using a single shared Socket.IO instance.

To preserve the autonomy of the chat servers, each will operate independently in their own namespace.

Building A Basic Chat Server



Thanks to the beauty that is Node.js & Socket.IO this is incredibly simple to do.
First we’ll setup our Node.js application and import the Socket.IO module.

var exp = require('express');
var app = exp.createServer();

Then we’ll create a single global instance of Socket.IO that we’ll eventually be using to share across multiple applications.

global.socket = require('socket.io').listen(app);
global.socket.set('log level', 1);
global.socket.set('transports', [ 'websocket', 'flashsocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']);

We’ll load in our config file, our router, and a custom chat-socket module we’ll define in a moment.

require('./app/config')(app, exp);
require('./app/server/router')(app);
require('./app/server/modules/chat-socket');

And then of course start up our server.

app.listen(8080, function(){
    console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
});

Creating The Server-Side Chat Socket Module

Our custom chat-socket module looks like this.

The first thing we need to do is define the namespace for this application.
We do this with the socket.of( ‘namespace’ ) method.

module.exports = function()
{	
    global.socket.of('/chat').on('connection', function(socket) {
 
	console.log("a user has connected to the 'chat' namespace");
 
    });
}();

Now we can add some code to track our connected users, broadcast their messages and listen for when they disconnect.

But first let’s create an array of colors that we’ll randomly assign to users as they connect to help tell them apart in the chat log.

var colors = ['#AE331F', '#D68434', '#116A9F', '#360B95', '#5F209E'];

Next we’ll create a “connections object” to keep track of our connected users.

var connections = { };

Our client side script will emit a “user-ready” event after someone successfully connects and is assigned a name. We’ll store that name value on the socket object server side for future use. We’ll also assign the newly connected user a random color from our colors array.

global.socket.of('/chat').on('connection', function(socket) {
    socket.on('user-ready', function(data) {		
    socket.name = data.name;
    socket.color = data.color = colors[Math.floor(Math.random() * colors.length)];
// let everyone else in the room know a new user has just connected //
    brodcastMessage('user-ready', data);
});

Let’s also listen for when they disconnect so we can remove them from our connections object and
alert the rest of our users.

socket.on('disconnect', function() {
    delete connections[socket.id]; 
    dispatchStatus();
    brodcastMessage('user-disconnected', { name : socket.name, color : socket.color });
});

dispatchStatus & broadcastMessage are just shortcuts to broadcast events back to the rest of the group.

function dispatchStatus()
{
    brodcastMessage('status', connections);
}
 
function brodcastMessage(message, data)
{
    socket.emit(message, data);
    socket.broadcast.emit(message, data);
}

When a user sends a message let’s append the color associated with their socket to the message so we can display the appropriate color in the chat log when we broadcast it back out to the rest of the group.

socket.on('user-message', function(data) {
    data.color = socket.color;
    brodcastMessage('user-message', data);
});

Everything all put together looks like this.

Hopefully nothing too daunting here. As you can see we’re just listening for when a user connects, disconnects and sends a message, and on each event we broadcast the event and its payload back out to the rest of the group.

Initializing Connections on the Client

When a user connects to our chat application we’ll give them a default name.
When they send a message we’ll retrieve this value and append it to the message being sent.

$('#name').val(Math.random().toFixed(8).toString().substr(2));

Before we can do that though let’s tell our client to listen on the same namespace as our Socket.IO application.

socket = io.connect('/chat');

It’s these separate namespaces that allow our chat applications to maintain autonomy while running as subdomains of our main Node application as we’ll see later.

Let’s send a message.

$('#btn-send').click(function(){ sendMessage(); })
var sendMessage = function() {
    socket.emit('user-message', {name : $('#name').val() , message : $('#msg').val() });
    $('#msg').val('');
}

sendMessage simply grabs the values stored in the #name input field and the #msg text area and builds them into an object that we can emit back to the server. After the message is sent we automatically clear the #msg text area so the user can type a new message.

The rest of the code on the client simply listens for incoming events and updates the conversation accordingly.

socket.on('user-ready', function (data) {
    $('#incoming').append('<span class="shadow" style="color:'+data.color+'">'+data.name +' :: connected</span><br>');
});
socket.on('user-message', function (data) {
    $('#incoming').append('<span class="shadow" style="color:'+data.color+'">'+data.name +' :: '+ data.message+'</span><br>');
});
socket.on('user-disconnected', function (data) {
    $('#incoming').append('<span class="shadow" style="color:'+data.color+'">'+data.name +' :: disconnected</span><br>');
});

Everything on the client put together looks like this.

Note, in our HTML page be sure to include the Socket.IO client library which is buried in the node_modules folder at the root of our server.

<script src="/socket.io/socket.io.js"></script>

And that’s really it! At this point we have a simple chat server listening for and broadcasting messages out to all connected users on our application’s namespace “/chat”.

Click here to check out a working version of our chat application.
Click here to checkout the project on Github and set up for the next step.

Socket.IO and Subdomains

The trick to getting Socket.IO working across subdomains is to declare one Socket.IO instance in your root application and then make that instance globally accessible to all applications on your server.

In the mydomain.com example application in the git repository,
we declare a globally accessible Socket.IO instance as so:

global.socket = require('socket.io').listen(app);
global.socket.set('log level', 1);
global.socket.set('transports', [ 'websocket', 'flashsocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']);

Then we setup our subdomain applications to run as virtual hosts within our main Express.js application.

app.use(exp.vhost('sub1.localhost', require('./subdomains/sub1')));
app.use(exp.vhost('sub2.localhost', require('./subdomains/sub2')));

Each of our subdomain applications is a full fledged Express application that contains its own chat-socket module to handle its connections and messages.

var exp = require('express');
var app = exp.createServer();
app.root = '/sub1.mydomain.com';
require(app.root + '/app/config')(app, exp);
require(app.root + '/app/server/router')(app);
require(app.root + '/app/server/modules/chat-socket');
module.exports = app;

Now what’s very important to note that is that each of these chat-socket modules defines their own unique namespace:

Inside /sub1.mydomain.com/app/server/modules/chat-socket.js

global.socket.of('/chat-sub1').on('connection', function(socket) ...

Inside /sub2.mydomain.com/app/server/modules/chat-socket.js

global.socket.of('/chat-sub2').on('connection', function(socket) ...

Then in each application’s corresponding client side JavaScript file, we tell connected clients to listen to our application’s namespace.

socket = io.connect('/chat-sub1');
socket = io.connect('/chat-sub2');

With each of these chat applications now running in their own independent namespace’s we can confidently run one Socket.IO instance across multiple subdomains without the fear of collisions.

As always feedback, questions and comments are welcome.