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

Share this article

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.

Frequently Asked Questions (FAQs) about Building a Web App with Backbone.js and Socket.io

What is the role of Backbone.js in building a web app?

Backbone.js is a lightweight JavaScript library that provides structure to web applications by supplying 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. It helps in organizing your code and makes it easier to manage. It is especially useful when dealing with complex user interfaces and large amounts of data.

How does Socket.io enhance the functionality of a web app?

Socket.io is a JavaScript library that enables real-time, bidirectional, and event-based communication between the browser and the server. It consists of two parts: a client-side library that runs in the browser and a server-side library for Node.js. Both components have an identical API. Socket.io is very useful in creating real-time applications like chat applications, real-time analytics, binary streaming, instant messaging, and document collaboration.

How can I handle events in Backbone.js?

Backbone.js provides an event handling system that allows you to bind custom events to your models. You can use the ‘on’ method to bind an event to a model and the ‘trigger’ method to trigger the event. You can also use the ‘off’ method to remove an event. This event system makes it easier to manage changes in your application and keep your user interface in sync with your data.

How can I use Socket.io with Express.js?

Socket.io can be easily integrated with Express.js, a popular web application framework for Node.js. You can create an Express.js application and then attach a Socket.io server to it. You can then use Socket.io to handle real-time communication between the client and the server. This combination of Express.js and Socket.io is very powerful and can be used to create complex web applications.

How can I use Backbone.js with a RESTful API?

Backbone.js is designed to work with RESTful APIs. You can use the ‘fetch’ method to retrieve data from the server and the ‘save’ method to save data to the server. You can also use the ‘destroy’ method to delete data from the server. Backbone.js automatically converts your model data into JSON format when sending it to the server and converts it back into model data when receiving it from the server.

How can I handle binary data with Socket.io?

Socket.io supports binary data, such as blobs and array buffers. You can send binary data from the client to the server or from the server to the client. Socket.io automatically takes care of encoding and decoding the binary data.

How can I handle errors in Backbone.js?

Backbone.js provides an ‘error’ event that you can use to handle errors. You can bind an error handler to a model using the ‘on’ method and then trigger the error event using the ‘trigger’ method. The error handler will be called with the model and the error as arguments.

How can I handle disconnections in Socket.io?

Socket.io automatically handles disconnections and reconnections. When a client is disconnected, a ‘disconnect’ event is emitted on the server. When a client is reconnected, a ‘reconnect’ event is emitted on the client. You can use these events to handle disconnections and reconnections in your application.

How can I extend Backbone.js models and views?

Backbone.js allows you to extend its models and views to create your own custom models and views. You can use the ‘extend’ method to create a subclass of a model or a view. You can then add your own methods and properties to the subclass.

How can I scale a Socket.io application?

Socket.io supports horizontal scaling through the use of multiple nodes and load balancing. You can use the ‘socket.io-redis’ adapter to enable communication between multiple Socket.io nodes. You can also use the ‘sticky-session’ module to ensure that all requests from a client are always sent to the same node.

Igor Ribeiro LimaIgor Ribeiro Lima
View Author

Igor works as UI Engineer on Avenue Code, and is a writer in his spare time. He enjoys learning new things, working on personal projects and contributing to open source community.

AurelioDbackboneBackbone.jsnodeNode-JS-Toolsnode.jsnodejsSocket.IOWebSockets
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week