Build a Realtime Event Bus with Firebase and Angular

Why an Event Bus?

“We think Firebase is just what we need to get rid of this crazy long polling stuff we are doing but what about HIPAA compliance?” This was the challenge that I found myself in awhile back as I was working with a health care startup. Realtime capabilities was going to take their application to the next level but they had some serious considerations around how they could store their data as it had potentially sensitive patient information. The solution is to use Firebase as an event bus that monitors a minimal amount of information on the Firebase servers and when a change is detected, your application then queries the main servers for the full object.

I have prepared a handy diagram to illustrate the event flow as you can see below.

Event Bus Diagram

To put this into concrete terms, I have a medical app that stores medical supply orders that I want to monitor the progress of the order as it is being fulfilled. All of the orders would live on the main server with a corresponding Firebase record that contained a unique ID and a timestamp. When an order was updated at the main server, the server would update the timestamp at the Firebase record using the REST API that comes with Firebase. All connected clients would detect that the timestamp of the Firebase object was updated and would then make a REST call to the main server to get the new and updated order.

For the sake of demonstration, I am using Firebase to play the part of the compliant server as well as the realtime event bus. Before we dig into the code, let us take a quick moment to look at our data structures at the servers.

The full object has an ID property as its key and a title and description property as you can see below.

full-data

The condensed, realtime object shares the same ID for its key and has a update_at property which contains a timestamp. Other than the actual ID to the object, there is no personally identifying data being stored in the realtime object.

realtime-data

The source files for this example are structured to represent two entirely separate entities. The two main folders in the project are the server folder and the realtime folder. The server folder represents the compliant server portion of the equation while the realtime folder represents the event bus portion of the equation.

file-structure

I believe we are ready to get started, so grab the code and let’s go!

Code

The Compliant Server

event-bus-step-one

We are going to start with the REST client that communicates with the compliant server. This majority of this section is going to be a composition of basic Angular techniques but still worth covering so that we can understand the entire lifecycle of the event bus.

The first piece to communicating with the compliant server is the RESTService which uses the $http service to perform standard REST calls. We have two convenience functions, getUrl and getUrlWithId to construct our endpoints. We are pointing to the base URI which we defined as a constant service called RESTFUL_URI in the server/server.js file. We are pointing to the orders endpoint and adding the .json extension because this is what Firebase requires.

I have used this technique on a few different backends and this part will change slightly depending on what you are using. The critical point is that you can make RESTful calls to your server.

And from here we have some pretty standard REST calls that are wrapped in the all, fetch, create, update and destroy methods.

app.factory('RESTService', function ($http, RESTFUL_URI) {
  var getUrl = function () {
    return RESTFUL_URI + 'orders.json';
  };

var getUrlWithId = function (orderId) {
    return RESTFUL_URI + 'orders/' + orderId + '.json';
  };

var all = function () {
    return $http.get(getUrl());
  };

var fetch = function (orderId) {
    return $http.get(getUrlWithId(orderId));
  };

var create = function (title, description) {
    var params = {
      title: title,
      description: description
    };
    return $http.post(getUrl(), params);
  };

var update = function (orderId, title, description) {
    var params = {
      title: title,
      description: description
    };
    return $http.put(getUrlWithId(orderId), params);
  };

var destroy = function (orderId) {
    return $http.delete(getUrlWithId(orderId));
  };

return {
    all: all,
    fetch: fetch,
    create: create,
    update: update,
    destroy: destroy
  };
});

Now that we have a way to communicate with the compliant server, it is time to build out the controller what will consume the RESTService service. In the ServerCtrl, we have create a few basic methods to marshall events from the view to our RESTService service. The methods in the ServerCtrl are fairly self-documenting and do exactly what they say. The one thing that I would like to point out is that I have left markers in the code for where we are going to hook up our calls to the realtime objects in the completed promises of our initial REST calls. We will fill those out in just a moment.

app.controller('ServerCtrl', function ($scope, RESTService, RealtimeService) {
  var server = this;

server.newOrder = {
    title: '',
    description: ''
  };

server.resetForm = function () {
    server.newOrder = {
      title: '',
      description: ''
    };
  };

server.getOrders = function () {
    RESTService.all()
      .then(function (result) {
        server.orders = result.data !== 'null' ? result.data : {};
      });
  };

server.createOrder = function (title, description) {
    RESTService.create(title, description)
      .then(function (result) {
        server.getOrders();
        server.resetForm();
        // Realtime stuff coming
      });
  };

$scope.updateOrder = function (id, title, description) {
    RESTService.update(id, title, description)
      .then(function (result) {
        // Realtime stuff coming
      });
  };

$scope.removeOrder = function (id) {
    RESTService.destroy(id)
      .then(function () {
        server.getOrders();
        // Realtime stuff coming
      });
  };

server.getOrders();
});

Firebase has provided realtime libraries for just about major language and web framework that exists but it is not as widely known that you can interact with Firebase endpoints via a REST API as well. This was implied as we are using Firebase to approximate our compliant server, but it becomes a bit more explicit when we use REST to communicate to an object that we are also using AngularFire to monitor in realtime. Our RealtimeService looks quite similar to our RESTService in terms of structure, albeit slightly more concise as we only need an update and destroy method. Notice that in the update method, we are setting the payload with a single parameter of update_at that is set to a current timestamp.

The reason we only have an update method and not a create method is that Firebase will go ahead and create an object at a specific endpoint if it doesn’t actually exist. Convenient!

event-bus-step-two

app.factory('RealtimeService', function ($http, FIREBASE_URI) {
  var getUrlWithId = function (id) {
    return FIREBASE_URI + 'realtime-orders/' + id + '.json';
  };

var update = function (id) {
    var params = {updated_at: new Date().toString()};
    return $http.put(getUrlWithId(id), params);
  };

var destroy = function (id) {
    return $http.delete(getUrlWithId(id));
  };

return {
    update: update,
    destroy: destroy
  };
});

With the RealtimeService in place, we can update our ServerCtrl to not only communicate with the compliant server but also with Firebase. When a new object is created, we will call RealtimeService.update and pass in the ID of the newly created object. In this case, it comes in the form of result.data.name since that is how Firebase returns the object. You will need to update this to match whatever format your compliant server uses.

server.createOrder = function (title, description) {
  RESTService.create(title, description)
    .then(function (result) {
      server.getOrders();
      server.resetForm();
      RealtimeService.update(result.data.name)
    });
};

It is a bit easier for the RealtimeService.update method as we already know the ID of the object that we just updated.

[cc lang=”javascript”]
$scope.updateOrder = function (id, title, description) {
RESTService.update(id, title, description)
.then(function (result) {
RealtimeService.update(id);
});
};
[/js]

The same goes for the RealtimeService.destroy method as we only need to send in the ID of the object we just destroyed.

$scope.removeOrder = function (id) {
  RESTService.destroy(id)
    .then(function () {
      server.getOrders();
      RealtimeService.destroy(id);
    });
};

This completes the necessary commentary on how the code works for the RESTful client that communicates with the compliant server. The three main parts to making this work are the RESTService, RealtimeService and the ServerCtrl which coordinates the two services.

We are coordinating the communication between the compliant server and Firebase from within a web application, but I have also seen this done entirely at the server level as well.

server-client

The Realtime Client

event-bus-step-three

We are now at the realtime, event bus portion of this example which is where things really start to get interesting! We are going to start by grabbing a realtime collection of our items via the RealtimeService using the $firebaseArray service. Creating a realtime collection in Firebase is a two-step process that involves instantiating a new Firebase reference and passing in the URI to the endpoint that contains your realtime data. In the case of the code below, we are pointing it to the realtime-orders node that exists at the FIREBASE_URI endpoint. We then instantiate the collection by passing our newly created ref to the $firebaseArray service which returns all of the items at that reference with realtime capabilities. We then make that collection available by returning it via the all method.

app.factory('RealtimeService', function ($firebaseArray, FIREBASE_URI) {
  var ref = new Firebase(FIREBASE_URI + 'realtime-orders');
  var orders = $firebaseArray(ref);

var all = function () {
    return orders
  };

return {
    all: all
  };
});

And now we can make our collection available to our view by binding it to the realtime.orders property in our RealtimeCtrl.

app.controller('RealtimeCtrl', function ($scope, RESTService, RealtimeService, CurrentOrderService) {
  var realtime = this;
  realtime.orders = RealtimeService.all();
});

We then bind to the collection in our view just like any other collection as you can see in the code below. We are building out a table with a standard ng-repeat=”order in realtime.orders”.


<table class="table edit">
<thead>
<tr>
<th>ID</th>
<th>Updated At</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="order in realtime.orders">
<td>{{order.$id}}</td>
<td>{{order.updated_at}}</td>
</tr>
</tbody>
</table>

The real “magic” of this example up to this point is in the two lines below. We are creating a new Firebase reference and then instantiating a realtime collection using $firebaseArray.

var ref = new Firebase(FIREBASE_URI + 'realtime-orders');
var orders = $firebaseArray(ref);

By creating a general collection at realtime.orders, we can know when our collection is modified as a whole but we do not have a good way of knowing what was updated at an atomic level. Because Firebase references are extremely lightweight, we are going to create an order directive to monitor each individual order for changes. This also allows us to encapsulate some additional functionality within our directive. To simplify things, we are going to examine the structure of our directive without any of the controller logic in it for now. In our directive definition object, we are setting scope:true so that each directive instance gets a new scope. We are also using the new bindToController syntax, to bind the orderId property to our controller. To complete our directive definition object, we are defining our controller and controllerAs properties.

app.directive('order',
  function ($rootScope, $firebaseObject, RESTService, FIREBASE_URI, CurrentOrderService) {
    var controller = function () {
      // Pending
    };

<pre><code>return {
  scope: true,
  bindToController: {
    orderId: '@'
  },
  controller: controller,
  controllerAs: 'orderCtrl'
};
</code></pre>
});

We are also injecting a few different services which we will elaborate on in just a moment. The first order of business is to create a realtime reference to the order object that our directive represents. We will create this almost the exact same way as we created our orders collection but we will be using the $firebaseObject service instead. The $firebaseObject service is useful for monitoring a single object instead of an array of objects as we saw in the $firebaseArray example above. Once we have created the correct Firebase reference, we pass that into the $firebaseObject service which will return an order object that has realtime capabilities.

app.directive('order',
function ($firebaseObject, FIREBASE_URI, RESTService, CurrentOrderService) {
var controller = function () {
  var orderCtrl = this,
    ref = new Firebase(FIREBASE_URI + 'realtime-orders/' + orderCtrl.orderId);

orderCtrl.order = $firebaseObject(ref);

return {
  scope: true,
  bindToController: {
    orderId: '@'
  },
  controller: controller,
  controllerAs: 'orderCtrl'
};
});

Our realtime order object has a few events that we can use to know when a change has occurred. Because a realtime object fires change events as it is loading, we will wait to start watching it until the $loaded event has fired. Once the $loaded event has fired, we then start to monitor it via the $watch method. When a change is detected, we will call the getOrder method and pass in orderId.

app.directive('order',
function ($firebaseObject, FIREBASE_URI, RESTService, CurrentOrderService) {
var controller = function () {
  var orderCtrl = this,
    ref = new Firebase(FIREBASE_URI + 'realtime-orders/' + orderCtrl.orderId);

orderCtrl.order = $firebaseObject(ref);

orderCtrl.order.$loaded()
    .then(function () {
      orderCtrl.order.$watch(function () {
        orderCtrl.getOrder(orderCtrl.orderId);
      });
    });

return {
  scope: true,
  bindToController: {
    orderId: '@'
  },
  controller: controller,
  controllerAs: 'orderCtrl'
};
});

And this entire example comes down to this one method. The entire event bus happens right here. In the orderCtrl.getOrder method, we call the RESTService to fetch the full object based on the id of the realtime object that was just updated. Once we have the full object, we are going to send it to the CurrentOrderService so that we can surface the details of what object was just updated.

event-bus-step-four

app.directive('order',
function ($firebaseObject, FIREBASE_URI, RESTService, CurrentOrderService) {
var controller = function () {
  var orderCtrl = this,
    ref = new Firebase(FIREBASE_URI + 'realtime-orders/' + orderCtrl.orderId);

orderCtrl.order = $firebaseObject(ref);

orderCtrl.order.$loaded()
    .then(function () {
      orderCtrl.order.$watch(function () {
        orderCtrl.getOrder(orderCtrl.orderId);
      });
    });

orderCtrl.getOrder = function (id) {
    RESTService.fetch(id)
      .then(function (result) {
        CurrentOrderService.setCurrentOrder(result.data);
      });
  };
};

return {
  scope: true,
  bindToController: {
    orderId: '@'
  },
  controller: controller,
  controllerAs: 'orderCtrl'
};
});

With our order directive created, we will update our tr element slight to include our new addition. We are instantiating the directive by adding the order attribute and passing in the appropriate orderId via order-id=”{{order.$id}}”.


<table class="table edit">
<thead>
<tr>
<th>ID</th>
<th>Updated At</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="order in realtime.orders" order order-id="{{order.$id}}">
<td>{{orderCtrl.order.$id}}</td>
<td>{{orderCtrl.order.updated_at}}</td>
</tr>
</tbody>
</table>

The realtime version of the RESTService is incredibly simple with a single fetch method but I wanted to include it here for reference purposes.

app.factory('RESTService', function ($http, RESTFUL_URI) {
  var getUrlWithId = function (orderId) {
    return RESTFUL_URI + 'orders/' + orderId + '.json';
  };

var fetch = function (orderId) {
    return $http.get(getUrlWithId(orderId));
  };

return {
    fetch: fetch
  };
});

The CurrentOrderService is a basic service that is essentially a getter and setter for the currentOrder object. The only twist to this is that we are using $rootScope as an internal event bus to broadcast to the rest of the application that there was an currentOrderUpdated event.

I rarely use $rootScope directly except for an event bus to broadcast events to the rest of the application. This is handy because Angular events only happen on $scope objects and by using $rootScope, we can guarantee that all scope objects are notified because they are all children of $rootScope.

app.factory('CurrentOrderService', function ($rootScope) {
  var currentOrder = {};

var getCurrentOrder = function () {
    return currentOrder;
  };

var setCurrentOrder = function (order) {
    currentOrder = order;
    $rootScope.$broadcast('currentOrderUpdated');
  };

return {
    getCurrentOrder: getCurrentOrder,
    setCurrentOrder: setCurrentOrder
  };
});

In our RealtimeCtrl, we then listen for the currentOrderUpdated using $scope.$on and use that event to grab the latest currentOrder object.

app.controller('RealtimeCtrl', function ($scope, RESTService, RealtimeService, CurrentOrderService) {
  var realtime = this;

realtime.orders = RealtimeService.all();
  realtime.currentOrder = CurrentOrderService.getCurrentOrder();

$scope.$on('currentOrderUpdated', function () {
    realtime.currentOrder = CurrentOrderService.getCurrentOrder();
  });
});

And then we can display the order details as we have in the HTML below.


<table class="table edit">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{realtime.currentOrder.title}}</td>
<td>{{realtime.currentOrder.description}}</td>
</tr>
</tbody>
</table>

realtime-client

Review

And there we have it! This is the technique that I use to provide realtime capabilities to compliant servers that store sensitive information. The general premise is that we are only storing an ID and a timestamp within Firebase and using that to serve as an event bus to the complete data at the compliant server.

It is my fondest desire that someone will take the material I share and use it to make lots of money. That would be super rad! With that said, I am not a compliance authority and / or a lawyer so double check with the powers that be before rolling your own version out to production.

Resources

Firebase REST API Documentation

AngularFire Documentation

AngularFire API

Code

Leave a Comment