Overriding Backbone.sync

Wednesday, March 18th, 2015

As a frontend developer, you might find yourself in a situation where you have to work with unRESTful endpoints. If you are working with Backbone, Backbone models follow traditional REST. As mentioned in the docs for Backbone.sync():

  • create → POST /collection
  • read → GET /collection[/id]
  • update → PUT /collection/id
  • patch → PATCH /collection/id
  • delete → DELETE /collection/id

If you need to modify what verbs and endpoints are used for methods fetch(), save(), and destroy(), you can override Backbone.sync on a per model basis to accomodate custom endpoints and HTTP verbs.

For example, imagine you have an Order model and it needs to make a POST request to /api/orders/cancelOrder when model.destroy() is called as opposed to the default DELETE request.

var Order = Backbone.Model.extend({
  sync: function(method, model, options) {
    switch(method) {
      case 'delete':
        options.url = '/api/orders/cancelOrder';
        return Backbone.sync('create', model, options);
      case 'read':
        options.url = '/api/orders/' + model.get('order_id');
        return Backbone.sync(method, model, options);
      case 'update':
        // handle update ...
      case 'create':
        // handle create ...
    }
  }
});

And it is as simple as that. And corresponding unit tests using Jasmine 2 for our custom implementation of Backbone.sync:

describe('Order', function() {
  describe('destroy()', function() {
    it('should make a post request instead of a delete request', function() {
      var spy = spyOn($, 'ajax');
      var order = new Order(/* data here */);
      order.destroy();

      expect(spy.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({
        type: 'POST',
        url: '/api/orders/cancelOrder'
      }));
    });
  });

  describe('fetch()', function() {
    it('should make a post request instead of a delete request', function() {
      var spy = spyOn($, 'ajax');

      var order = new Order({ id: 99 });
      order.fetch();

      expect(spy.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({
        type: 'GET',
        url: '/api/orders/99'
      }));
    });
  });
});

Disclaimer: Any viewpoints and opinions expressed in this article are those of David Tang and do not reflect those of my employer or any of my colleagues.

comments powered by Disqus