Stop Using Backbone As If It Were A Stateless Web Server

In the web development world with MVC based back-end servers, nearly everything is kicked off with routes. Look at rails for example. If you want a list of items, you hit /items and the router executes the index method on ItemsController. Add a new item, view an item, edit an item and post the updates back to the server – even deleting an item works with a route.

Unfortunately I see the same patterns emerging in a lot of sample code for javascript MVC frameworks, like Backbone. Stop doing that. Now. You shouldn’t be using routes for functionality that can be achieved with a simple method call, a command, or an event.

Why It Works For The Server

A web application is a stateless system. Your browser does not have a live connection to the server for any more time than is needed to transfer the rendered html, css, javascript, images, etc to your browser. Once that data transfer is done, you are viewing the page on your local system. The server is not holding a ton of resources in memory, allowing you to manipulate them directly through your browser. You’re manipulating a representation of those things, that has been turned into a combination of technologies that your browser knows how to deal with.

Because of the stateless nature of web servers, it makes sense for every action you want to do to have a route associated with it. The server needs to have some context to tell it what you are trying to do, so it can figure out what code you’re trying to run. All of this is necessary because the browser is disconnected from the server. The server doesn’t know that you’re looking at item #1 or the list of items. It knows nothing about what you’re doing until your browser tells the server to do something.

State. Do You Speak It?

A Backbone app is closer to a desktop client application than a web application in a few respects. Not the least of which is that it has state. The objects and data that are loaded in memory will stay in memory. You can rely on the item that you loaded being there when you need it again, without having to reload based on an id every time you need it.

Because of the stateful nature of a Backbone application, there are times when it doesn’t make sense to use a route, though it technically works.

Problem: Routing A Delete

I’ve seen this a number of times – and have built it at least once, myself. When a developer who normally works with rails or another MVC server technology gets to the point where they need to delete one of the Backbone models, they add a route for delete. Then they use a link in the html to hit the route and cause the delete to happen.

The delete route and view often look like this:

SomeModel = Backbone.Model.extend({});

SomeCollection = Backbone.Collection.extend({
  model: SomeModel
});

SomeView = Backbone.view.extend({
  el: "#some-model",
  template: "#some-template",

  render: function(){
    var html = $(this.template).tmpl(this.model);
    $(this.el).html(html);
  }
});

SomeRouter = Backbone.Router.extend({
  routes: {
    '#/delete/:id': 'delete'
  },

  delete: function(id){
    model = someCollection.find(id);
    model.destroy();
    $("#some-model").remove();
  }
});

With an HTML layout that looks like this (after being rendered):

<div id="some-model">
  <p>display some data from the model, here</p>
  <a class="delete" href="#/delete/1">delete</a>
</div>

Yes, this is functional. You can click on that link and it will delete the model in question. The rendered view for that model will also be removed from the HTML that is displayed on the screen. As functional as this is, though, there are several problems with it.

Browser History

One of the features that we get with Backbone’s router is the ability to control the browser’s history and the back button. Every time we send the browser to a new url#route, Backbone records it in the browser’s history. This allows us to move backward and forward in the application, using the browser’s backward and forward navigation button.

When we use a delete route, we get the deletion stuffed into our browser’s history. If we hit the back button, after navigating to another url#route, the router will try to find and delete the model again. It gets even worse if we are routing deletes of groups of things.  Assume that we routed to #/delete/green in order to delete all items that are colored green. Then the user adds several new items that are green. Now that they are done, they click through the back button history in order to get to where they started. Along the way, they hit the #/delete/green route again, and all of the work they had just done is destroyed.

To prevent these bad scenarios and prevent unwanted errors from models not existing when the router fires the delete code again, we have to put null checks around things. This makes our code a little uglier, a little less readable and gives us more to maintain over time.

Bookmarks and Copy & Paste Urls

Another advantage of Backbone’s router and url#routes, is the ability to copy & paste the entire url or bookmark it, and get back to where we left at any time in the future. When we open that bookmark or paste the url with the url#route in it, the Backbone router will kick off the route’s code. Here, we end up in the same scenario as the browser history issue.

Unnecessary Lookup To Find The Model For Deletion

Backbone is a stateful framework. This means that we have whatever objects are instantiated hanging around and waiting to do work or have work performed on them. By using a route to find the model that we want to delete, we are ignoring the state that our Backbone application has already provided in order to look up a model that is already in memory, waiting to be used.

The result of this is negligible in terms of memory and performance, in the example of deletion. However, the problem extends beyond the simple model and into the views.

Breaking Encapsulation To Remove The View

The last line of the router’s delete method removes the view that was displaying the item because when you’re deleting an item, you will likely want to remove it from the view as well. By using a jQuery selector to find the view and remove the HTML that represents the view from the DOM directly, though, we are breaking the view’s encapsulation and creating spaghetti code which will likely become difficult to maintain over time.

A Backbone view provides a significant amount of functionality and capabilities. One of the convenience features that is provided for us is the `remove` method of the view. This method, according to the documentation, calls `$(this.el).remove()`. This is essentially the same code that we have called in our router’s delete method. However, this remove method is encapsulated within the view and uses the context and knowledge that the view holds in order to do the delete. Even if this convenience method doesn’t exist in your version of Backbone, it is 1 line of code to add it and allow work against your view to be encapsulated correctly.

By making this call outside of the view, we are breaking encapsulation. We are also opening up the possibility of bugs being introduced to the app in ways that are difficult to track down. If we have code strewn throughout the app that removes HTML elements, but we are not cleaning up the view objects that represent (and own) those elements, there could be problems. If a view tries to access an element that is no longer there, the work it’s trying to do will at best, not do anything. At worst, it will cause unexpected errors and potentially ruin the work that the user has been doing.

Other Issues

While the list of issues I’ve described is fairly comprehensive of the potential problems, it’s not complete. There are near countless combinatorial problems that can be put together between all of these and other potential issues that I haven’t expressed.

Solution: Let The View Be In Control

Given the number of problems that have been identified, it should hopefully be apparent that a route for a delete is probably not the best thing to do. Fortunately, we can solve these problems by building our Backbone views the way they were meant to be built, allowing them to encapsulate control of a model, including the model’s deletion.

Here’s an example of the code that we can use to allow deletion of the model via the view, directly:

SomeModel = Backbone.Model.extend({});

SomeCollection = Backbone.Collection.extend({
  model: SomeModel
});

SomeView = Backbone.view.extend({
  el: "#some-model",
  template: "#some-template",

  events: {
    "click a.delete": "delete"
  },

  render: function(){
    var html = $(this.template).tmpl(this.model);
    $(this.el).html(html);
  },

  delete: function(e){
    e.preventDefault();
    this.model.destroy();
    this.remove();
  }
});

And the view can be simplified a little, too:

<div id="some-model">
  <p>display some data from the model, here</p>
  <a class="delete" href="#">delete</a>
</div>

You’ll notice that this is roughly the same amount of code. There may be 1 or 2 lines less in the new version, but that’s negligible at best. We don’t get any advantage from this perspective. However, we do get a number of advantages with regards to the previous problems that I described.

No Browser History For The Delete

We’ve changed the delete link in the HTML from “#/delete/1″ to “#”. This could cause a browser history entry to be created, if the browser is currently pointing to a route other than “#”. However, there is a line of code in our delete method that will prevent this from happening. The first line, “e.preventDefault();”, tells jQuery to prevent the link from causing the browser to change it’s url. Thus, when we click the delete link, the item will be deleted but we will not navigate to “#” and therefore will not have a new route fired or a browser history entry created.

No Bookmark or Copy & Paste Urls For Deletion

Again, we’ve changed the delete link in the HTML from “#/delete/1″ to “#”. This will prevent a bookmark or url copy & paste from triggering any deletion. Since you can’t create a bookmark to a specific line of javascript code, or copy & paste a url that starts out on a specific line of javascript code, we don’t have to worry about this problem at all.

No Unnecessary Lookup To Find The Model For Deletion

We’re taking advantage of Backbone’s stateful nature, in this case. When the view is instantiated and renders the HTML output, it stays around for us to use.

By using the declarative events of the view, we have bound the delete link to the view’s delete method with jQuery. When the link is clicked, the view is still around for us to execute code, and still has a reference to the model that was rendered. This allows us to call `.destroy` on the model directly, without having to do any lookups.

No Breaking Encapsulation To Remove The View

Lastly, we’re letting the view take care of itself, as it should. When the delete link is clicked and after the model is destroyed, the view closes itself. Notice that we’re not using a jQuery selector, either. We’re letting Backbone’s built in `.remove` method handle that for us.

Since the view is handling the destruction of the model – which is owns – and itself, there are no encapsulation breaks. Our view can then let itself fall out of scope and we no longer have to worry about any zombie view objects looking for elements that aren’t around anymore.

Other Niceties

Beyond the issues that I’ve addressed, there are additional benefits of building views and delete functionality in this manner. Backbone contains a tremendous amount of power and provides a lot of features for us to take advantage of. Every time we stay within the boundaries that Backbone outlines, it makes it that much easier for us to use the additional features provided to us.

Where Routing Works With Backbone

There are plenty of examples of where routes work well with backbone, of course. Imagine your building a large website that contains articles, for example. You could try to load all of the articles into the browser, all at once, using Backbone’s models and collections. It’s trivially simple to load a Backbone collection, after all. But loading all of these articles into the browser would cause the browser to slow down to a crawl, eat up a ton of memory and possibly crash the browser depending on the quality of browser and memory handling. Whatever the actual effects are, they would likely not be good.

The router is perfect for situations like this. You don’t need to load all of the articles from the server all at once. Instead, you can use routes to figure out which article the user wants and only load the detail for the one specified from the server or other data store.

Beyond This Simple Example

I’ve outlined a very simple example of a route that enabled some functionality while creating a slew of potential problems. This is a very simple example, as well. Imagine what could possibly go wrong if you have a very large Backbone app with nested views and the ability to move forward and backward in those views.

While there are some very distinct advantages of using a router, it should not be our default go-to object to enable functionality. MV* frameworks, like Backbone, give us the opportunity to bridge the gap between the web and the thick clients. We need to take off our stateless-web-server glasses and realize that Backbone opens a world of different principles and patterns. We need to look to the thick-client, desktop and native-mobile-device applications for guidance in some of these areas. Not every pattern we find will apply, of course. But many of them will, and they will help us produce much longer-lasting, maintainable solutions for our interaction-heavy web pages.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in AntiPatterns, Backbone, Javascript, JQuery, Model-View-Controller, Pragmatism, Principles and Patterns. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://citizenparker.com Scott Parker

    Hear hear! I couldn’t really wrap my head around what I was supposed to do with Backbone routes, but then I spent some time with SproutCore and state charts. After that, I find myself thinking of routes as very similar to states in a Statechart, and serve remarkably well for modeling state.

    Something like a delete then is a transition from one state to the next, but should itself *not* be a state.

    • http://mutedsolutions.com Derick Bailey

      that’s a good way to look at it. i really need to dig into sproutcore. i’ve read some of the tutorials, and like what I see but i haven’t had time to try it out yet.

    • http://erichocean.com Erich Ocean

      FWIW, I’m bring SproutCore’s statechart library to Backbone.js (and similar MVC frameworks). Exact same feature set as SproutCore in about 7KB packed + gzipped.

      Code will be up at http://statechartjs.org shortly.

  • http://kazimanzurrashid.com Kazi Manzur Rashid

    Excellent article, I think none of the writable operation should be exposed as Route e.g. /new or /id/edit is okay which brings the new/edit screen of the model but it should never expose /create or /update as route.

    I think following is a typo on “Solution: Let The View Be In Control” para.

    SomeModel = Backbone.View.extend({});

    should be

    SomeModel = Backbone.Model.extend({});

    Again, keep up posting on backbone, so that we can learn.

    • http://mutedsolutions.com Derick Bailey

      thanks. :) 

      and thanks for the tip on that typo! i’ve updated the gist, but it doesn’t seem to be reflected in the post, yet. there’s probably some caching that needs time to clear out.

  • http://blog.wekeroad.com robconery

    I swear I don’t want to be contrarian – so I’ll lead with the punchline: I don’t like blanket statements that sit on top of an architectural theory. And this post sounds that way… BUT before you get all up in my face :) I’ll say that YES – I agree with your premise… if I can reword it.

    Routes aren’t for actions. Meaning if *all* you want to do is #delete a record – righto! No route is needed to fire that action off to the server. I completely agree with you.

    HOWEVER (and I think you’ll agree with this) – most of the time a #delete is a lot more than just a simple HTTP DELETE. There are most likely associated records, possibly a notification you need to run, etc.
    The really cool thing about Backbone (and other MVC frameworks) is that FOR ONCE in a very long long while devs get to think about the idea of *communicating more*. Thinking about the *what it is* the user wants to do.

    Delete a Product? What about the orders? What about existing carts? What about inventory?

    For this – #delete works perfectly. You can show them all the things that might be deleted/altered/somehow affected by this action.

    Now I KNOW that you’re saying “Duh Rob… I KNOW DUDE” but here’s the thing: if someone thinks twice about this approach – they likely will try and wedge in something fugly to try and “comply”. 

    Also – another thing to think about – if someone *did* have #deleted/99 in their history and it item 99 wasn’t there (cause they deleted it) – what a great time to remind them! This is the user’s app – a simple “Dude – you deleted this remember? Either that or I can’t find it” will really help out in some cases.

    I don’t mean to split hairs here :) I promise. The thing I think I’d like to focus on is that Backbone is a UX tool – the “U” meaning “User”. Sure, a #delete might be a PITA for you but if it serves the U’s purpose in any way – do it!

    • http://mutedsolutions.com Derick Bailey

      you’re pointing out the difference between a #/delete route and a `destroy` action, right? that’s a good distinction to make and i like your example. 

      when you need to confirm a delete, display additional things that are going to happen when the delete occurs, etc, then a delete route and a destroy action would be a good thing.

      “Routes aren’t for actions” says it perfectly.

  • http://blog.wekeroad.com robconery

    I just had another thought :). Bear with me… the very idea that there would be a view for a deleted item… that it’s even an *option* causes people to think a bit about what that might mean.

    As I was putting my laptop away (after entering that last comment) I started to think… “well normally yeah you wouldn’t show a View for a deleted item… I wonder if I said that…” and it hit me: that’s precisely Dereck’s point! But in the other direction…

    In the stateless web world we just throw up our hands and say “404 YO” if someone tries to access a deleted record. In the pseudo-stateful Backbone world… hmmmm.

    A lof of people prefer logical deletes. What if you were an admin and went to an old link (say… #show or #edit) and the item was deleted? You could redirect to #delete/id and show them some pretty user-friendly information: “You deleted this record on xx/xx/xxxx – restore?”.

    That’s hot. For a lot of reasons. And the funny thing is I didn’t really explore this much until I read your post here and was thinking “hmmm – it seems that might be a bit too simplistic of a statement”.

    Hope you don’t mind my long comments. Backbone (and JS in general) really give a huge leg up with respect to UX. I think it’s OK to let your mind go free just a bit more.

    • Battaile Fauber

      “For this – #delete works perfectly. You can show them all the things
      that might be deleted/altered/somehow affected by this action.”
      That’s not something I want in my history, and going back to it is either going to give unexpected results or require some fugly hacking to show what you’d expect (the listing prior to the delete having taken place)

    • http://mutedsolutions.com Derick Bailey

      “well normally yeah you wouldn’t show a View for a deleted item…”

      ah! yes. that. thank you :) i had not come to that conclusion consciously but it was floating around in the post and in my head, somewhere. now that you’ve said it, i can see how that’s a big part of the perspective i was taking. i tend to approach backbone as a winforms developer rather than a web developer, and i would never have a view for a deleted item in a winforms app – except in the case of a logical delete / restore, like you’re talking about.

      i also had not though about logical deletes when writing this. i was very focused on the trivial delete example. are we doing logical deletes (marking a record as “deleted” or “inactive” in the database) or actually deleting the record? 

      handling the back button when you have a logical delete, a delete route and confirmation provides a number of opportunities and possible paths in the flow of the app. would you have the destroy action flip the screen to a “deleted.” confirmation message, and then allow the back button to display this as a reminder that something was deleted?  would you display the “restore?” message for an admin? and/or link / route them to #/restore/id to do the restore? and there’s still more possibilities.

      … that’s a lot of stuff to think about. thanks for the input! :)

  • Tof

    Really interesting. Obvious, but worth reading anyway.

  • Anonymous

    Derick – very interesting post.  In my own play projects with Backbone, I’ve tended to only use routes for navigation-related state, not destructive editing of state (deletes/changes/adds).  I think Rob’s point about logical delete/restore is pretty interesting.  It does mean you’ll have some additional complexity to cover (for which many of us are more than happy to write a framework to handle!).  I think as a general rule it’s a “don’t pull the trigger while aiming at your head” approach to avoid storing model state changes as part of the route/history (unless you have a model representing navigational-related history), and that writable actions should be tied to uniquely identifiable entities to make your operation idempotent.  I’m excited to see these kinds of discussions more and more – since I think the implications that come with client-side frameworks like Backbone are going to challenge us and our users to rethink some of the assumptions that web 1.0 and 2.0 have thrust upon us.  I get giddy thinking about the implications of client side routing, while taking your advice into account, Rob’s points about the potential richness of logical delete/restore – AND throw websockets into the mix.  Welcome to the age of “application as a resource”. :-)

  • Anonymous

    This is an issue you have in message based systems ( which are stateless as well).  Messages need to be idempotent, which is a fancy way of saying that if a message is sent more than once, it will have the same effect.  In you delete case, if you have delete in your page history and you navigate back to it, the case of it already being deleted needs to addressed.

    The approach@robconery:disqus  mentions would indeed be idempotent, if it shows a message like “record already deleted”

  • http://www.easyate.com/ Viceroy

    Thanks for you sharing.That is good article.

  • Anonymous

    Good stuff. I remember realizing this while I was working on my Backbone app too. It takes some getting used to.

    Somewhat unrelated, I wonder what you guys think about this.

    I feel like calling e.preventDefault() and adding href=”#” to all of my links that trigger Backbone actions is a lot of duplicated code, so I’ve kind of taken to a different pattern. Instead of adding the href attribute and calling preventDefault, I just  add “a { cursor: pointer; }” to my CSS.

    Thoughts?

    • http://mutedsolutions.com Derick Bailey

      i like it. i’ve used the css cursor trick once or twice, but it doesn’t usually cross my mind for some reason, when I’m building backbone apps

      it’s a good way to cut down on the code, but may have some drawbacks if you need to worry about accessibility. a screen reader, for example, won’t know to tell the person about the link. i realize that this may be an uncommon case for backbone apps, but it may be important.

    • Anonymous

      You can also do this which avoids the need to call preventDefault():

      <a href=”javascript:”> … </a>

      • http://mutedsolutions.com Derick Bailey

        that’s interesting… i don’t think i’ve seen that one before. is that supported by all the major browsers?

        • Anonymous

          I’ve not had any problems using it. Really it’s just a shortened version of <a href=”javascript:void(0)”>

  • A’braham Barakhyahu

    Others have echoed my sentiments.  Web devs that have been so used to stateless nature of the web do need to look at clientside-apps like desktop applications.  Good post.

  • Dude

    isn’t this stuff obvious? i’m shocked, honestly

  • Libor Nenadál

    Well quite a lot of words for one simple thing – use route for routing to items (views), not for actions. ;-)

  • http://twitter.com/GertSallaerts Gert

    Great article, I completely agree with you. I wouldn’t use the class attribute to bind the actions though, it should be for CSS only imho. What I do is:

    Delete me

    and

    events: { ”click a[data-action=delete]“: “delete”  }

    This just makes more sense to me personally.

    • Jeremy Schwartz

      Devil’s advocate – class makes more sense:
      1) I’m assuming class selectors are more efficient than data-attribute selectors, as some browsers have the native `getElementsByClassName`
      2) using class can double-duty with visual cues — not only do you target it for behavior, but you can use the same CSS to (as you say, more appropriately) style it with a delete icon, etc.
      3) fits with the OOCSS (object-oriented css) pattern

      I usually prefer something more indicative of the fact that it is a delete action — `btn-delete` or `actn-delete` rather than just `delete`, so you can take advantage of OOCSS like:

      {a href=”btn btn-delete”}Delete{/a}

      then CSS
      .btn { width: 16px; height: 16px; text-indent:… }
      .btn-delete { background-image:url(delete-icon.png); }

      then JS
      $(‘.btn-delete’).click…

      or even
      $(‘.btn’).click( // all buttons fire, then you can check for extended class to determine what happens specifically

  • bob

    i finally read your post. I was wondering if it creates a larger memory footprint to bind each individual event on a view. Ie.You have a list of elements. Each element has a delete button. You can bind the delete event to each element vs binding the delete event to the list and have event propagation handle each elements delete.

  • Rocket

    I’ve seen another approach where the views for models are being stored in a cache. To remove a view you just look it up in the list and call .remove().This way we have symmetry – both adding and removing of views is done on the same level, in collection view. As opposed to adding views in collection view, and removing in model view (as you described). Which way do you think is better?

  • http://twitter.com/Mauceric Christian Mauceri

    Really great article, I agree with Gert Delete me makes more sense to avoid to get back to the root in a modal view for instance.
    Anyway thanks a lot !

  • Victor Dzundza

    Thanks man !