Push notifications using express.js and socket.io

So we built a real-time bidding system! Why? Because one of our clients needed it. Our customer bridges the gap between vendors and customers. (Cannot reveal more specifics like the domain etc.)

  • Customer requests an order and vendors bid for that order.
  • Customer should be notified in real-time about any bids.
  • Customer should be able to negotiate with any of the bidders.
  • A negotiation request should be sent to all bidders, so they can be competitive.
  • A vendor should see a negotiation request.
  • A customer can confirm a bid for a vendor.
  • That vendor should get immediate notification and should confirm that bid!
  • blah, blah, blah…

Since this was web application, using websockets was the need of the hour! What we needed was to finalize was putting it all together. We had a choice of socket.io, express.js or backbone.js. (Feel free to remind me of others). Since our Rails server (v3.1) was configured with MongoDB (via Mongoid), we had to get this in place too.

Option 1

Use node.js and its mongodb node_module to setup a notification service between users. A user could connect to the node server (register his login name) and we could control messages to and from that user. The problem is however that the notifications are generated after Rails request processing – this is not a chat service. Shoot!

Option 2

Use express.js to setup a small rack application and route requests to that.  But I would have to manage flow of information – write models, controllers etc. AND it still doesn’t solve my problem of notifications being generated after Rails request processing. Further more, I would need to listen on the client side anyway!

What we did

We run a node server in addition to a Rails server. The node server is powered by socket.io and express.js.

  • socket.io is used for easy notification management from clients.
  • express.js route was to listen for notifications from the Rails server.

So, when a vendor or customer logs in, we connect to the node server and maintain that connected socket connections hash. The express.js routes would be for notifications generated from the Rails server. Using the node connections  hash, we can decide whom to send notifications! The added advantage is that I am not dependent on the database. Here is the gist if you want to see it all. Here are the deeper details:

First we need to configure our node server with express.js AND socket.io. The express.js server is listening on port 13002!


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

app.use(express.bodyParser());
app.listen(13002);

Now, lets see how we setup the routes for listening to notifications from the Rails server. We have a generic message sender method which takes the :action and the : recipient whom the message is intended for. The body contains whatever we want to send, typically json data. We shall soon see what the connections contain.


app.get('/', function (req, res) {
 res.send(404);
});

app.post('/message/:action/:to', function (req, res) {
 target = connections[req.params.to]
 if (target) {
  connections[req.params.to].emit(req.params.action, req.body);
  res.send(200);
 }
 else
  res.send(404);
});

We see that connections is critical here. This is where we hook up socket.io into express.js. The connections is a Hash that contains all the connected websockets!


var connections = {}

io.sockets.on('connection', function(socket) {
 socket.on('username', function(username) {
  connections[username] = socket;
 });
});

When a websocket connection is received, we save the connection data into our server connections hash. That way we know who is connected and who should be sent the notification!

Hey! How did the web-sockets get connected anyway? Simple – here is the HTML client side Javascript!


:javascript
  var socket = io.connect('#{NODEJS_HOST}');

  // Connectivity
  socket.emit('username', '#{current_user.name}');

  socket.on('reconnect', function () {
    console.log('Reconnected to the server');
    socket.emit('username', '#{current_user.name}');
  });

  socket.on('reconnecting', function () {
    console.log('Attempting to re-connect to the server');
  });

  // Custom Messages

  socket.on('bid', function(data) {
    $('#notifications').append("<div class='notifications'>" +
    "Bid: Rs." + data.amount + " for " + data.customer +
    "</div>");
  });

What happens here is that when the socket.io gets connected, the socket emits a method with the logged in users name! Thats the name we collect and store in the server connections hash!

We can now customize ANY message like we have done for ‘bid’. It can be passed data directly and can be processed. So, the only missing piece of the puzzle now is how we create and fire notifications from the Rails server!

require 'net/http'

module FX
  module Messenger

    def notify(action, user, data)
      url = "#{NODEJS_HOST}/message/#{action}/#{user}"
      res = Net::HTTP.post_form(URI.parse(URI.encode(url)), data)

      # 200 implies successfully sent.
      # There is nothing we can do if the targe user is not online(404)
      # For any other error, raise Exception
      unless ["200", "404"].include? res.code
        raise Exception.new("Error: #{res.code}")
      end
    end

A sample example of what we have done is on github. Its still in the basic stage (extracted from what we built) but I plan to add full fledge bidding control to it soon.

Ciao!

21 thoughts on “Push notifications using express.js and socket.io

    1. @hanumakanth – Firing ActiveSupport::Notifications is unrelated to this, as these notifications are internal registered events. To connect internal notifications to this, you can always register a subscriber and implement the callback to send a custom notification to send to the express.js service.

  1. Hey nice article. But I have one question why don’t you use Juggernaut for the same. Any reason? Also firefox 3 or IE7, IE8 don’t support Web sockets so how do you handle those browsers?

    1. @Santosh,
      There are various options for push notifications, there APE, Juggernaut, simple XMPP server too.

      Positives of my approach: less resource overhead, faster processing (because its Javascript evented server) and good support on Heroku.

      Negatives: Old browsers do not support this — but then do we care? Maybe sometimes, but then I believe we should let the good times roll and move ahead with newer technology solutions.

      1. Actually, socket.io degrades gracefully to use JSONP, AJAX long polling, some flash solution and a few others so this should work in IE7-8 and FF3 too.

  2. Can you tie in TXT-msg, IM, email as fall backs for ‘404’ to prompt users to connect and participate ?

    1. @AbsAnalyst This is simple. We can fallback in the node server as well as from the Rails app.

      # For any other error, raise Exception
      unless ["200"].include? res.code
      # Write special code here for "404" to IM or email.
      # else raise exception.
      raise Exception.new("Error: #{res.code}")
      end

      We could alternatively let the invoking function choose the fallback

  3. This is exactly what I am looking for my application. I was trying using ajax calls. I guess this is better option though. Thanks

  4. This is pretty insecure, isn’t it? there’s no authentication server side, which means anyone can just edit the username in the client side code to get notifications from somebody else.

    1. Well, to keep things simple, I have used the username – A more secure way is simply to generate server side hash (SHA) on the fly for every logged-in user session and save that instead.

  5. I am thinking to build an app using ajax call and lot of jquery code. but your post save my time. Thanks

      1. I am making 2 app servers for a common web app, node.js for push notification (and polling) and everything else on the Rails server.
        How can I achieve this?

      2. Here are some approaches:
        + Use express.js (on your node.js host) and let your Rails server communicate with it using RESTful API.
        + Use Redis PubSub and publish / subscribe events to it (both node.js and rails have good bindings with redis).

  6. Keeping the connects Hash does solve the targeting pretty easily… but how do you plan to scale it to multiple instance of Node/Socket servers??

  7. Was the rails part already constructed as a legacy system? you could have wrote the entire app in node

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.