Oct 17, 2012

Backbone.js - Creating a RESTful CRUD application

I've been trying to build a CRUD application using Backbone.js and was able to retrieve a set of records into a collection using the fetch method. To perform other operations i.e. Create, Update and Delete I could always invoke Backbone.sync but I was exploring on the lines where this is performed implicitly. The fetch method sends an implicit GET request on url specified in the Collection, similarly I was looking for other methods that allow you to send POST, PUT and DELETE requests to the url. While I was building this application, I did come across a condition where Backbone was not sending a request. I was finally able to figure out as to why that happened and then it was a simple fix in my Backbone application as well as in the REST service.

Create - Add a New record to the Collection:

Adding a new record to the Collection is fairly simple. Call create method on the Collection and you would see that a POST request is sent on the URL specified in the Collection. The POST request sends the Model data in the request body in JSON format (Content-Type as application/json):
carCollectionInstance.create({BRAND: 'Hyundai', MODEL: 'i20', COLOR: 'GREY'});

Retrieve - Retrieving a set of records and adding it to the Collection:

Retrieving a set of records by sending a GET request to the server and then parsing the response using parse property is also very simple. I have explained this in my last blog post - 'Backbone.js - Parsing the response from the Server'

Delete - Deleting the models in the Collection:

This is where I had spent most of my time in figuring out as to why Backbone wouldn't send a DELETE request to the server. There were two issues here; firstly the records that the REST service returned in JSON format didn't contain the 'id' key:

{BRAND: "Ford", MODEL: "Figo", COLOR: "RED"}
{BRAND: "Honda", MODEL: "CRV", COLOR: "GREEN"}
....

I was trying to retrieve a Model from the Collection by calling collection.at(index) and then calling destroy on the Model. The destroy method indeed removed the Model from the Collection but didn't send a DELETE request to my REST service. I was frustrated to see this behavior, then I referred to the docs and understood that if a call to Model.isNew() evaluates to true then the DELETE request wouldn't be sent. Also, according to the docs a Model is considered to be new (isNew() returns true) if the Model doesn't contain an 'id' attribute. This meant that I had to change my REST service and ensure that each Model had an 'id'.

After updating the server side logic, I tried calling destroy on the retrieved Model. Even this time it didn't send a DELETE request. Soon I realized that when ColdFusion serializes any type to JSON, it changes the casing of its keys to uppercase. This is not an issue with Backbone, but in fact a bug in ColdFusion. I had to change the casing of the keys in the parse function before adding the retrieved models to Collection. The next attempt destroy the Model in the Collection and sending a DELETE request to the server worked fine. Backbone would send a DELETE request to the server by specifying the Model's 'id' as a PathParam in the url:

http://localhost:8500/rest/Car/CarService/5

The REST resource would then retrieve the 'id' value which is available as an argument.

Update - Updating the models in the Collection:

The Update mechanism is very much similar to Delete. Here a PUT request is sent to the server specifying the 'id' value in the url and also the Model data in the request body.

resultModels = carCollectionInstance.where({brand: 'Honda'});
for(var i=0; i< resultModels.length; i++) { resultModels[i].set('color','BLACK'); resultModels[i].save(); }


In this example, I'm trying to retrieve a set a records in the Collection where 'brand' is 'Honda'. This would return an array of Models which can then be updated by setting the necessary keys (model.set('key','value')) in the Model. On calling save on the Model, a PUT request would be sent to the server.

This was a good exercise for me and Backbone does a good job by implicitly calling Backbone.sync method  with appropriate HTTP methods. So far I'm very impressed with what Backbone has been able to provide. I've not explored Router and Views in Backbone yet and I'm sure there will be tidbits that I'll discover while learning them. Stay tuned.

8 comments:

  1. Hi,

    I have the same problem with de DELETE method.
    In the following link:
    https://github.com/documentcloud/backbone/issues/37


    References the attribute "idAttribute".
    With "idAttribute" you do not need to define an id in the Model.

    Example:
    var Car = Backbone.Model.extend({
    idAttribute: "chasis_num",
    initialize: function(){
    ...
    ...
    }
    })

    var car1 = new Car({ chasis_num : '1234567' })
    ..
    //add car1 to collection
    ..
    car1.destroy()

    *) In destroy of car1 will send a DELETE:
    http://localhost:8500/rest/Car/CarService/1234567

    Reference
    http://backbonejs.org/#Model-idAttribute
    I dont know if "idAttribute" fits your needs.

    Thanks for the tutorials of Backbone.js!!
    Martin.

    ReplyDelete
  2. Hi,

    I have the same problem with de DELETE method.
    In the following link:
    https://github.com/documentcloud/backbone/issues/37

    References the attribute "idAttribute".
    With "idAttribute" you do not need to define an id in the Model.

    Example:

    var Car = Backbone.Model.extend({
    idAttribute: "chasis_num",
    initialize: function(){
    ...
    ...
    }
    })

    var car1 = new Car({ chasis_num : '1234567' })
    ..
    //add car1 to collection
    ..
    car1.destroy()

    *) In destroy of car1 will send a DELETE:
    http://localhost:8500/rest/Car/CarService/1234567

    Reference
    http://backbonejs.org/#Model-idAttribute
    I dont know if "idAttribute" fits your needs.

    Thanks for the tutorials of Backbone.js!!
    Martin.

    ReplyDelete
  3. Thanks Martin, for pointing that out. Yes, I read the documentation, perhaps I should have mentioned that in my post. In this scenario I was using a Car collection where none of the attributes would qualify as an identifier for that Model and hence I had to use the 'id' attribute.

    ReplyDelete
  4. Hi Sagar,

    You have mentioned that, you faced a situation when the backbone was not sending the GET requet to the server. Can you specify the exact problem you faced. In my app, i am also making a GET to a REST Service to fetch some data, but i am getting a cross domain error "XMLHttpRequest cannot load http://myHost:5751/rides/getallrides. Origin null is not allowed by Access-Control-Allow-Origin."I dont know, how to tackle this from backbone.

    ReplyDelete
  5. Himanshu, 

    If you are sending a request to different server then you are likely to get this error in the browser. There are two options that you have - if you have access to the remote service then add a Access-Control-Allow-Origin in the response header (refer to the post http://www.sagarganatra.com/2011/04/html5-xmlhttprequest-2-cross-origin.html) or use jsonp i.e. say if you are using $.ajax then specify the datatype as jsonp. Hope this helps.

    ReplyDelete
  6. Thanks for your quick response. :)

    i have read about using jsonp in my ajax calls. So finally settled on it. And implemented the ajax call inside an overriden collections.fetch function.
    Though, i am confused why such an issue was not addressed in the tutorials i referred. either the REST services were used were using node.js or some other platform.

    ReplyDelete
  7. Hi Sagar.. nice post!


    I'm having similar issues at the moment. But i'm considering a different solution. One where I just fire a DELETE request to the collections endpoint.. WITHOUT an 'id' of the spefic model to be deleted. A 'delete all' I suppose.


    Have you ever tried anything like that?

    ReplyDelete
  8. In a traditional CRUD application you would not use 'DELETE ALL' records. However, you can implement a method in the collection that sends an Ajax request to the server. The backend service can implement a method which deletes all the records from the table.

    You can also call clear on the collection but that would not update the data on the server.

    ReplyDelete