JavaScript
Article

Build a Web App with Backbone.js and Socket.IO

By Igor Ribeiro Lima

This article was peer reviewed by Thomas Greco and Marc Towler. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

As many of you know, Backbone.js is a well-known MV* framework. It’s hosted on GitHub and it gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.

In this article we’ll use a built-in feature of Backbone called Events to implement an asynchronous messaging paradigm to avoid coupling. The idea is to separate groups of code that are highly dependent on one another.

The example I’m going to show is a graph visualization tool where data is beautifully synced cross users. The use of WebSockets will make it possible to open an interactive communication session between the user’s browser and a server.

The goal is to keep the example as simple as possible. The concepts learned here will help you a lot in reducing coupling. In addition, this is a very helpful approach for building extensible, flexible, and maintainable code. At the end of our experiment, we’ll have the following result:

graph visualization tool

Backbone.js

Backbone.js is a framework to build single-page applications by providing models, views, controllers, collections, and custom events. Its structure helps us to separate the user interface from the business logic. In this article I’ll introduce you to only on some of these elements but in case you want a more in-depth guide, I suggest you to read the article “Backbone.js Basics: Models, Views, Collections and Templates“.

A Model represents data and it can be created by extending Backbone.Model:

var MyModel = Backbone.Model.extend({
  initialize: function () {
    console.log('model initialized');
  }
})

A View is a way to organize the user interface into logical views, backed by models. It doesn’t contain HTML markup, but just the logic behind the presentation of the model’s data to the user. To create a view you need to extend Backbone.View as follows:

var MyView = Backbone.View.extend({
  el: 'div#my-view-container',
  initialize: function (options) {
    this.model = new MyModel()
    console.log('view initialized')
  }
})

Events is a module that can be mixed into any object, giving the object the ability to bind and trigger custom named events. Both model and view have this module event which allow us to bind events to a model or view. A common pattern is creating views that listen on changes in models. This technique is usually intended to allow the view to automatically re-render itself when the underlying data changes.

To give you an example of how these elements work together, I’ve created a demo on CodePen.

See the Pen XXpwMQ by SitePoint (@SitePoint) on CodePen.

Whenever we change the input, the view changes our model. Once hitting the OK button, the view renders the new id value.

Realtime Communication between Server and Client

WebSockets are an advanced way that makes it possible to open an interactive communication session between the user’s browser and a server. With this API, users can send messages to a server and receive event-driven responses without having to poll the server for a reply. This contract is an agreement that describes rules and expected behavior, and entails action from both sides. When these two pieces link up through an API, it acts as a glue for integrating server and client.

In a bridge analogy, once in place, data and instructions can cross over the bridge. For instance, the server receives an instruction, performs an action, and after processing the instruction, it returns information back to the Web application. A Give-and-Get between parties. With this abstraction layer (the API), we hide complicated things of web services, with no need to understand further details on how the server has performed the action.

Socket.IO is a JavaScript library for real-time web applications. It enables bidirectional communication between web clients and server. Both sides have identical API and are event-driven like Node.js. To open this interactive communication session between the browser and a server, we have to create an HTTP server to enable real-time communication. It will allow us to emit and receive messages. The socket is the object that handles this communication between web clients and server.

The code below creates that server using socket.IO with the Express framework.

var express = require('express'),  
    app     = express(),
    http    = require('http').Server(app),
    io      = require('socket.io')(http);

http.listen(process.env.PORT || 5000, function(){  
  console.log('listening on *:5000');
});

After creating that server, we have the io class that allows to setup a callback which takes a socket as a parameter. With this socket object we are able to create the connection between the user’s browser and a server.

io.on('connection', function(socket) {  
  console.log('a user connected');
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
});

In the browser, the client links up to the server calling the io function that returns a socket for a connection:

var socket = io("http://pubsub-example-with-backbone.herokuapp.com/");

Said that, let’s create a simple communication that data and instructions can cross over the bridge.

var socket = io("http://pubsub-example-with-backbone.herokuapp.com/")

// retrieve all nodes already stored in the server
socket.emit('retrieve-all-nodes')

$("button#add-new-node")
  .on('click', function () {
    socket.emit('add-node', {}, function (obj) {
      console.log(obj)
    })
  })

socket.on('node-added', function (node) {
  var $nodes = $("ul#nodes").append(`
    <li id="${node.id}">
      <span>${node.id}</span>
      <button>x</button>
    </li>
  `)

  $nodes
    .find(`li#${node.id} button`)
    .on('click', function () {
      socket.emit('remove-node', node)
    })
})

socket.on('node-removed', function (node) {
  $("ul#nodes").find(`#${node.id}`).remove()
})

With this code in place we are able to build the following demo:

See the Pen mVRYMW by SitePoint (@SitePoint) on CodePen.

In case you want to learn more about Socket.IO and Express, I suggest you the following articles:

Backbone.js with Socket.IO

In this section I’m going show you an example of how to use Socket.IO with Backbone.js:

var MyView = Backbone.View.extend({
  el: '#my-backbone-app',
  events: {
    'click button#add-new-node': 'addNewNode'
  },
  initialize: function (options) {
    var view = this
    view.socket = io("http://pubsub-example-with-backbone.herokuapp.com/")
    view.socket.emit('retrieve-all-nodes')
    view.model = new MyModel()

    view.socket.on('node-added', function (node) {
      var $node = $(`
        <li>
          <span>${node.id}</span>
          <button>x</button>
        </li>
      `)
      view.model.set(node.id, $node)

      view.$el.find("ul#nodes").append($node)
      $node.on('click', function () {
        view.socket.emit('remove-node', node)
      })
    })

    view.socket.on('node-removed', function (node) {
      view.model.get(node.id).remove()
      view.model.unset(node.id, {})
    })
    console.log('view initialized')
  },

  addNewNode: function () {
    this.socket.emit('add-node', {}, function (obj) {
      console.log(obj)
    })
  }
})

And this is the result:

See the Pen QydXwo by SitePoint (@SitePoint) on CodePen.

PubSub with Backbone.js

PubSub is an asynchronous messaging paradigm. It gives a feature that provides us to avoid coupling. Coupling is when a group of code is highly dependent on one another, which means that if a piece of code changes, then an update of everything that uses this piece of code is needed.

PubSub is a pattern that has synchronization decoupling. It uses an event system like the way a radio works: a radio station broadcasts (publishes) and anyone can listen (subscribes). Moreover, rather than talking to other objects directly, you can publish messages on a shared radio station. That event system allows us to define events that can pass arguments containing values needed by the subscriber. Backbone.js makes this event system implementation pretty straightforward. You just have to mix Backbone.Events into an empty object, in this way:

var EventChannel = _.extend({}, Backbone.Events);

At this point you can use the standard trigger and on methods to publish and subscribe to messages:

var socket = io("http://pubsub-example-with-backbone.herokuapp.com/")

var MyPubSub = $.extend({}, Backbone.Events)
MyPubSub.on('disconnect', function () {
  console.warn('disconnected')
})

socket.on('node-added', function (node) {
  MyPubSub.trigger('node-added', node)
})

socket.on('node-removed', function (node) {
  MyPubSub.trigger('node-removed', node)
})

MyPubSub.on('retrieve-all-nodes', function () {
  socket.emit('retrieve-all-nodes')
})

MyPubSub.on('add-node', function (node, cb) {
  socket.emit('add-node', node, function (obj) {
    if (cb) {
      node.id = obj.id
      cb(node)
    }
  })
})

MyPubSub.on('remove-node', function (node) {
  if (node && node.id) {
    socket.emit('remove-node', node)
  }
})

Doing that, then you are now able to remove socket.io from our Backbone View.

var MyView = Backbone.View.extend({
  el: '#my-backbone-app',
  events: {
    'click button#add-new-node': 'addNewNode'
  },
  initialize: function (options) {
    var view = this
    view.model = new MyModel()

    MyPubSub.on('node-added', function (node) {
      var $node = $(`
        <li>
          <span>${node.id}</span>
          <button>x</button>
        </li>
      `)
      view.model.set(node.id, $node)

      view.$el.find("ul#nodes").append($node)
      $node.on('click', function () {
        MyPubSub.trigger('remove-node', node)
      })
    })

    MyPubSub.on('node-removed', function (node) {
      view.model.get(node.id).remove()
      view.model.unset(node.id, {})
    })

    MyPubSub.trigger('retrieve-all-nodes')
    console.log('view initialized')
  },

  addNewNode: function () {
    MyPubSub.trigger('add-node', {}, function (obj) {
      console.log(obj)
    })
  }
})

The goal is to avoid dependencies between modules. Each module can have a channel like a radio station firing the events (the publishers) and any other modules can listen to their events wishing to receive notifications (subscribers).

The result obtained is the following:

See the Pen gPgNpz by SitePoint (@SitePoint) on CodePen.

Graph Visualization Example

Our graph visualization uses two modules on the client-side: one for drawing directed graphs and another for storing and fetching data. The graph drawing module uses a tool named Force Editor. This module, which we call ForceView in the code, lets us position the nodes of the graph in two-dimensional space in a simple and intuitive way. The storage module, which we call DBaaS, uses Socket.IO to enable realtime, bi-directional communication between web clients and server. None of them, ForceView and DBaaS, know about any of the other. Their behaviors are all in isolation.

Both modules are set up in a publish/subscribe style to avoid dependencies. They use an event system in the same way as a radio works, with a radio station broadcasting (publish) and radio receivers listening (subscribe). Rather than talking to another directly, each module publishes their messages on a shared “radio station” firing off events on their own channel, and the other can also listen to any other channels for events.

The only dependency here is on each radio channel that has a very small API. What matters is which message the channel is triggering, and to ensure that the system reacts to the events correctly. If they trigger an event and give the right thing, the system will work as a whole. Take a look at the images below to see which events are being emitted from each of these modules.

dbaas module

force view module

A Backbone View acts as a mediator between ForceView and DBaaS. This allows us to decompose everything into usable small pieces and then make those small pieces work beautifully together. In this way, the code becomes simpler to understand and easily maintainable.

For instance, if we want to customize it just a bit to suit a particular taste, we can easily pick up any module and change it any way we like. We could replace the graph visualization by other graph libraries, e.g. jqPlot, Dracula, ArborJS, sigmajs, RaphaelJS and so on. Or we can use any realtime database like Firebase, Appbase, Neo4j, TitanDB, etc. The good news is that we just need to change a single file in order to migrate to another library. The image below illustrates the interaction between the Backbone View and these two modules.

Please note that we are not using any database. Data is being stored in memory. The way we decoupled the code allows us to connect to any kind of database.

PubSub as a radio station

Running Our Graph Visualization Example Locally

The entire code is available on GitHub. You can clone the repository or download the code.

git clone https://github.com/sitepoint-editors/pubsub-example-with-backbone.git

Then execute npm install from a console to install all the dependencies.

Next, execute node server.js to start the application.

Head to http://localhost:5000 with your browser to see the running application. If you just want to see the application in action, you can find a demo here.

Conclusions

Finished! We just used a built-in feature of Backbone to implement a PubSub pattern. In addition, we used this pattern to represent and store graph data in real time where data was beautifully synced across users. As you’ve seen, we have mixed few interesting concepts in a great example to see decoupled pieces of code working together.

The next step now is to customize it and store the data in a database instead of in memory. But we’ll probably discuss about customization in one of the upcoming posts.

Feel free to share your comments in the section below.

  • http://www.joezimjs.com Joe Zimmerman

    I really like the use of an Event Bus here as an abstraction between the WebSocket communication layer and the rest of the app, but I’d prefer to have a collection/model interacting with the Event Bus and the View should be interacting with the models’ and collections’ events.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.