Building Epic Win With Backbone.js

I’ll just skip to the chase and start this post off by saying that our Windows VPS Hosting service, Epic Win Hosting has publicly launched! I know that it has been a while, and believe me, we wanted to launch it sooner. We have been in private beta, working out the kinks, but now things have come together and so now you can go check it out!

But at the end of the day, this is a technical blog and so while you might be interested in the product, I’m sure that you are at least equally interested in the technology behind it! So I want to give you a quick overview of how we used Backbone.js with ASP.NET MVC in order to build pieces of the Epic Win UI.

Architecture Overview

Before we get into Backbone, I wanted to get you a quick overview of the stack that we are using. The front end is built with ASP.NET MVC hosted on IIS, the backend uses MySQL for data, MongoDB for logging and auditing (although the more I use it, the more I want to put in it!), and RabbitMQ for messaging.

After all, Epic Win is an automation platform built on top of a few different services. So we need to store state about our user resources, and we need to invoke third party apis in a reliable way. We do this using a message queuing system that we wrote on top of RabbitMQ. Well, enough about the architecture, let’s get on with Backbone!

Front End UI With Backbone.js

When we first launched Epic Win as a private beta we built out the interface in order to create an instance. After we built the server instance interface, we decided that we needed to leverage a framework which would allow us to easily update the UI on the fly without a bunch of custom callbacks and hand written jQuery calls. I had been looking at Backbone.js for a while, and since it is both powerful and simple, I thought it would be an excellent time to leverage it.

When we set out to build the interfaces for DNS and backups we built them on top of backbone. We are hoping to rewrite the instance interface at some point, but we have yet to get on that yet. So, let’s get on with the show!

Backbone.js Setup

The first think you need to do is head over to the Backbone.js website and download the JavaScript file. You’ll also need to download and add a link to Underscore.js since this is a dependency of Backbone.

Backbone.js Introduction

Backbone.js is a JavaScript library which gives you a rich framework for writing client heavy applications. Backbone.js calls itself an MVC framework, but this originally stood for Models, Views, and Collections. This is because originally there was no controllers in the framework, but this has since been changed. Originally you had your model, which is what you expect, just a class that holds your data. You can declare a model like this (notice there are no properties, since this is dynamic):

var DnsZone = Backbone.Model.extend({});


Now that we have a model declared, we can now declare a collection. Think of a collection as a unit of work class that you might find in an ORM. It allows you to sync with the server and pull down models, add models and sync them back to the server, delete models, etc…

var DnsZoneStore = Backbone.Collection.extend({
    model: DnsZone,
    url: "/dns/zones"
});

Here we have declared a collection and told it what type of model it is going to hold, and then we have also told it the root url that we are going to use in order to sync with the server.

Controllers

Next let’s go ahead and setup a controller, so that when we hit the page we aren’t just running code when the document ready fires, but instead we have something to build upon:

var DnsController = Backbone.Controller.extend({
    routes: {
        "": "zones",
        "hosts/:id": "hosts"
    },
    initialize: function (options) {

    },
    zoneList: new DnsZoneStore(),
    hostList: null,
    zones: function () {
        var view = new DnsZonesList({ collection: this.zoneList, el: $("#zones") });
        this.zoneList.fetch();
    },
    hosts: function (id) {
        this.hostList = new DnsHostStore([], { zoneId: id });
        var view = new DnsHostsList({ collection: this.hostList, el: $("#hosts") });
        this.hostList.fetch();
    }
});

There is a lot going on here, and I have left in some of the code for the host entries, but let’s go ahead and break it down. First we start off with the "routes", the default route "" is what fires off when you load the page with no client side routes. The "zones" string after the default route is what tells the controller which method to invoke for this route. The second route "hosts/:id" is a client side route which invokes the "hosts" method. The colon in front of "id" signifies that it is a parameter, and as you can see the "host" method takes a parameter called "id".

Notice earlier I said the routes were client side, and by that I mean that a route will look like:

http://www.epicwinhosting.com/dns#hosts/2

You see, the route is after the hash, which means that it is a fragment identifier and not actually part of the url, this means that changing it doesn’t cause the page to reload but does cause a history entry to be registered by the browser (this is very useful!).

Notice that when we invoke the default route the "zones" function is invoked. The first thing that the method does is create a "DnsZonesList" which as you might guess from the name of the variable it is assigned to, it is a view.

Views

In Backbone.js a view is slightly different from what you might think of as a view in a web MVC framework. It is closer to a view in Cocoa if you’ve done any iPhone or OS-X development. Essentially a view is the whole page, or  an element on the page. Views can be inside of views. So a page can be a view, but a button can also be a view. In this case, the "DnsZoneList" view represents the grid which displays the list of DNS zones. I say it "represents" it because really the view is some HTML that is in the page, the view is a chunk of JavaScript which hooks it all up:

var DnsZonesList = Backbone.View.extend({
    events: {
        "click #new-zone-button": "add"
    },
    initialize: function () {
        _.bindAll(this, "render", "add");
        this.collection.bind("all", this.render);
    },
    render: function () {
        var table = $("#zones-table tbody");
        table.empty();
        this.collection.each(function (dnsZone) {
            var zoneEntry = new DnsZoneEntry({ model: dnsZone });
            table.append(zoneEntry.render().el);
        });
        return this;
    },
    add: function () {
        $("#new-zone-form-dialog").dialog("open");
    }
});

First we hook up the events and the only one we have on this view is the "new-zone-button" button. You see that when this button gets click the "add" method is invoked.

Next we have the constructor, which is designated by "initialize". First we use underscore.js and call "bindAll" on the "render" and "add" methods. This lets us use "this" within these methods and it will refer to the class. Secondly we grab the collection which was passed from the controller and we bind all of the events coming from the collection, which is to say anytime a model is added, removed, updated, etc…, to the "render" method. This way, anytime the collection is modified, the grid renders itself! Very nice!

The "render" method is where things start to get a bit more interesting. We grab the table that we want to render inside of and loop through each model in the collection and create a "DnsZoneEntry" view passing the model. This view represents a single line in the grid. Then we append the rendered element from the view to the table. The DnsZoneEntry view looks like this:

var DnsZoneEntry = Backbone.View.extend({
    tagName: "tr",
    events: {
        "click .delete": "deleteZone"
    },
    initialize: function () {
        _.bindAll(this, "render", "edit");
    },
    render: function () {
        $(this.el).append($("#dns-zone-item-template").tmpl(this.model.toJSON()));
        return this;
    },
    deleteZone: function () {
        forms.confirmDialog("Are you sure you want to delete this zone? This cannot be undone.", function () {
            this.model.destroy();
        });
    }
});

As you can see here we are defining a default tag as a table row. Next we hook up a click event on the "delete" class which invokes our "deleteZone" method. What is interesting about this view is that you’ll notice that our events only bind to items within the current view! So we wire up our click event to the "delete" class and we know that when our event fires, all we have to do is call "destroy" on the current model and our view updates as expected!

Another interesting tidbit in this view is how we are using jQuery templates in order to render the model into the table. In this case the template is in the page itself, but there are a number of ways it could be loaded. The template simply looks like this:

<script id="dns-zone-item-template" type="text/x-jquery-tmpl">
    <td>
        <a class="edit" href="#hosts/{{= id }}">{{= Domain }}</a>
    </td>
    <td>{{= DefaultTTL }}</td>
    <td>
        <button class="delete">Delete</button>
    </td>
</script>

Oh, and ignore the weird template tags, we are using spark view engine which uses the default tags that jQuery templates uses, so we had to use the alternate style.

Wiring It Up To The Server

Now that we have looked at the front end, we need to see how it all wires up to the backend. Notice that in our case we haven’t specified any urls other than the base url in the collection. This is because Backbone uses convention to know what urls to form. These are the urls that it uses

  • POST / – create new item
  • GET / – list all
  • GET /{id} – get a single item
  • PUT /{id} – update a single item
  • DELETE /{id} – delete an item

So as you can see, it is more about the http verbs than it is about the url. On the ASP.NET MVC side, these actions are pretty easy to define (I’ve removed some code so that id doesn’t get in the way):

[HttpGet]
public ActionResult List()
{
    return Json(zones, JsonRequestBehavior.AllowGet);
}

[HttpDelete]
public ActionResult Delete(int id)
{
    return Json(new { id });
}

[HttpPost]
public ActionResult Create(DnsZone postData)
{
    return Json(new { id = dnsZone.Id });
}

In our application, we aren’t currently updating dns zones, so there is no Update method, but it would only be a matter of deserializing the incoming Json into a server side model, which is pretty easy.

Wrapping It Up

As you can see, Backbone.js is simple to get setup and working, allowing you to very easily create highly interactive web pages. I would recommend that you go check out the Backbone.js website since they have very thorough and detailed documentation.

Also, this is my first site I’ve built with Backbone, so I’m sure there are places where I could have done a few things easier. If you have any experience with Backbone, I’d love to hear your feedback!

Be Sociable, Share!

21 comments

  1. Man, this stuff implemented with KnockoutJS would be so much cool than Backbone ;)

  2. Justin Etheredge

    @thelinuxlich You know, we looked at KnockoutJS and came to the conclusion that Knockout and Backbone don’t overlap exactly. Knockout does way more with declarative binding, while Backbone stays out of the views. Knockout doesn’t have a mechanism to persist to the server while Backbone does. Knockout also doesn’t have any form of controller in order to handle multi-page apps. All in all I think they are both awesome frameworks, but at the time Backbone did more of what we needed.

  3. Site looks good. Prices also seem high. I won’t advertise other hosters here as I’d like to see Epic Win succeed, but drop me a line Justin if you wish and I’ll show you what I mean.

  4. I like the site design. I just wanted to drop a quick note that I hard a hard time reading it with the striped lines… I think it would be a little easier with just the grey and no lines – or a thinner lower contrast line.

    Good luck!

  5. When I heard you used backbone I was hoping you’d release a post like this….and it went waaaay over my head. I do not think that $20 for a Windows VPS is too much. Good job.

  6. Just a note on persisting to the server as a reason to choose backbone over knockout – why would you want to persist your view model to the server? Shouldn’t you be generating a create/update command to persist changes? Or is this a crud app?

  7. Nice article. I hope this is just beginning of you host plans. I would love to see a Heroku for .NET apps. Any plans?

  8. Justin Etheredge

    @Luke Well, a few things. First of all, the models we have very closely mimic the interface, since the interface is built specifically to edit them. Secondly, Backbone isn’t really “persisting” them to the server. If these were specifically view models, they could be mapped to commands and then synced to the server through backbone, or they could be mapped to the server via backbone and then mapped there. The whole idea is that backbone handles the eventing and transport, but you are free to implement any other part as you see fit. But remember, always use the simplest thing that will work.

  9. Justin Etheredge

    @Rob Right now we don’t have any plans to become Heroku. Not that it is fully ruled out, but that isn’t the direction we are currently headed in.

  10. I am now interested in BackboneJS… and can see what all the fuss is about. One thing I don’t see are any screenshots of your management UI on the Epic Win Hosting website…. the site says it’s easier to manage than with the EC2 UI directly, but without seeing the proof, I imagine I’m not the only one who isn’t convinced.

  11. Thanks for sharing how you built this – I’ve been doing MVC and recently got into Backbone.js

    Nice win! Glad to see you pull these in together

  12. Great article; I was originally looking in KnockoutJS but will give BackboneJS a chance too. As for Epic Win Hosting… I’m struggling to understand the value proposition?

    It looks like you’re putting a simplified VPS hosting front-end on top of Amazon services? But then why not just go with a VPS to begin with because Amazon’s prices are nowhere near competitive with the typical VPS provider? Simplified VPSs are not really Amazon’s value proposition. Amazon’s power and complexity IS the value proposition so the only thing I see Epic Win providing is a smoother way to get up and running with Amazon services initially, but it’s not clear at all whether it’s possible to shift from the Epic Win ship over to the raw Amazon back-end when you’re ready.

  13. I like that you’re promoting the use of backbone.js, it’s a great thing. I too am confused like Ryan, but for a different reason. I took a poke around http://epicwinhosting.com/ and couldn’t find any reference to backbone.js at all.

    Is it hidden behind the paywall and you’re only using backbone for the “app” portion of the site? If so, it’s hard to see what you built and want to showcase in the blog post ;)

    Also, the whole frontend of the site could have been a single page app with routes for each of your unique urls. Howcome you didn’t use it there?

  14. Justin Etheredge

    @Ryan The value proposition is there, it allows people to quickly and easily get up an running on EC2, while still maintaining a lot of the excellent features of EC2. You don’t just give all of that up. You still get things like snapshot backups, access to load balancers, free external firewall, etc… Not all of it has been exposed at this point, but I’m sure it will be added over time. Another thing it provides is a flat pricing model. A lot of people aren’t comfortable with the pay as you go pricing model, and just want a predictable price every month. As far as moving your account account, well Amazon allows you create a private AMI from an instance and then share that instance with another account. So, for your machines this would provide a way to move them across accounts.

  15. Justin Etheredge

    @David Sorry, yes, the backbone driven portion is behind the pay wall. That is part of the reason that I wrote up this post! Feel free to contact me via my contact page if you have any additional questions or run into any problems.

    As far as the marketing site goes, I didn’t build that site, so I don’t know any of the decisions that went into it.

  16. Justin,

    Did have to change the MVC routes to get backbone requests to go to different actions in the controller.

    e.g. The ‘Delete’ action.

    How did you get MVC to route to the correct action based on the httpverbs?

    If yes, could you provide/show that code?

    Thanks.

  17. Could you state the version of Backbone you are using at the time of writing this tutorial. Because Backbone seem to change quite rapidly.

    I am having trouble with getting something like this to work:

    this.hostList = new DnsHostStore([], { zoneId: id });

    I am using version 0.5.3 of backbone.

  18. Justin Etheredge

    @Silent Works This post was from April, so the version of Backbone was probably around 0.3.3. The version number didn’t change for quite a while. Although the code above just looks like you are creating an object instance and passing in an empty model array along with an options hash. This should still be valid. What kinds of problems are you seeing?

  19. It’s creating a new collection rather than getting a existing data from the collection. I can’t seem to find any up to date document outlining how to accomplish this.

  20. Justin Etheredge

    The best thing I can tell you to do is to check out the many backbone.js samples available at the bottom of the official documentation: http://documentcloud.github.com/backbone/#examples

  21. What was the learning curve, how many developers did you have working at this site, how long did it take and what did it cost? You don’t have to answer the last question if you answer the first three, I can figure it out.

Leave a comment