Tuesday, 27 May 2014

Server side communication with Backbone.JS


Server side communication? 
                   
                           Backbone.js has some special way to communicate with the server. All the server communications are very implicit and achieved through backbone's model/collection. If you're well versed in network's core knowledge, I'm sure that I've brought you an excellent feast here. Before enter into our course, if you're looking for the core concepts on backbone.js kindly go through this first course written here.

Tools required:
  1. Latest Netbeans IDE which is available here.
  2. Google Chrome browser configured with Netbeans Connector plugin.
  3. Install Node.js server configured with MongoDB in your machine.
Before proceeding further please have look at this video, how to design a restful api here. And look at here for how to implement restful api through node.js server here

And the kick-off for this course:
  • Please download our last project here. Since our course is entirely depend on this project only.
  • Now, open the Netbeans IDE, select new project icon, choose HTML5 from the 'Categories' menu and HTML5 Application with Existing Sources from the 'Projects' menu. Then in the 'Site Root' box browse the download file's app folder. i.e., If my file is in Downloads folder, then choose it like Downloads/backboneApp/app. The second box is 'Project Name' in which enter your project name. In our example, the project name is 'MyFirstApp'. Click finish.
  • Next, find our project named MyFirstApp from the project explorer window. Press run button on the Netbeans IDE. In a moment you are able to see the following home screen in Google Chrome.
  • Make sure that in the above home screen, by pressing goSecond button allows you to go to the second page which is shown below 
  • By pressing goback button from the above screen, you can go back to home screen. In which write something in the text box press enter, this action enlist your text below. Look at the below screen to see the list of strings enlisted by me. (Find that the enlisted strings are circled).

If you're able to see the above three screens, then you're ready to proceed further. If not please go through the steps mentioned above again.

Now, please download the rest api file here. This web service file is written using Node.js server and MongoDB database. We're going to use this restful api's to explore the server side communication with backbone.js.

  • After downloading the backboneAPIs.js file, start the MongoDB server then run the backboneAPIs.js file using node server by the following command in terminal. (Note that we already configured Node.js server and MongoDB in our machine.)
  • $> node ~/Downloads/backboneAPIs.js
       Please note that, my file backboneAPIs.js is downloaded/located in Downloads folder in my machine. If you downloaded/located in different folder, please navigate to that folder then, run it using node server like $> node backboneAPIs.js 
  • If you're able to get this following reply from the terminal after running, you've no issues to proceed. 
         myapp listening at http://127.0.0.1:8080
  • Next, go to Netbeans IDE, choose MyFirstApp, there go to scripts->Models->mymodel.js
  • Now do the following changes inside the backboneApp.Models.MymodelModel = Backbone.Model.extend({ }) function.

         //6
         urlRoot: 'http://127.0.0.1:8080/employees',
        

         //1
         initialize: function() 
         {
            //Error handling during validation as well as server communication
            this.on("invalid" ,this.validationErrorHandler, this);
            this.on("error", this.serverErrorHandler, this);
         },


         //4
         defaults: 
         {            
            "lname": "Not specified",
            "age" : "Not specified"
         },
      

         //5
         validate: function(attrs, options) 
         {           
            if(attrs.fname == null )
            {   
                return "First Name Shouldn't Be Null !!!";
            }
           
         },
       

         //8
         parse: function(response, options)  
         {
            
            //Parse the response you've got.
            console.log("inside parse method, reponse "+JSON.stringify(response));
            return response;
         },
        

         //2
         validationErrorHandler: function(model, error)
         {
 console.log("validation error handled, model" + JSON.stringify(model) + "error"+  JSON.stringify(error));
         },


         //3
         serverErrorHandler: function(model, error)
        {
 console.log("server error handled, model" + JSON.stringify(model) + "error"+ JSON.stringify(error) );
         },


         //7
         sync: function(method, model, options)
        {
            switch(method)
            {
               case 'create': 
                             //Do appropriate action when 'post' new record.
                             //Most often we used to add the data locally before posting it to
                             //server. This is called 'data persistence'.
               console.log("create method is called, and the data to be synced is "+JSON.stringify(model));
                             break;
               case 'read':
                            //Write the activities before reading the data from server.
                             console.log("read method is called, and the data is "+JSON.stringify(model));
                             break;
               case 'update':
                            //If you want to sync the updated data with the local data, do it here.
                            //Note that you're doing it before updating it in the server.
                             console.log("update method is called, and the data is "+JSON.stringify(model));
                             break;
               case 'delete':
                            //Write the activities before deleting a data from the server.
                            console.log("delete method is called, and the data is "+JSON.stringify(model));
                            break;             
                       
             }            
             return Backbone.sync.apply(this,[method,model,options]);
            
        }

       The explanation for the above snippet, as listed below. Please note that, each method is numbered in a particular order. Match the explanation with the reference number prefixed with the methods.

  1. initialize method, this method is used to bind the events with it's event handlers. In our example we used this initialize method for error handling. There are two types of errors being caught , subsequently the appropriate actions has been carried out in the above snippet. In the initialize method, have a look at the two lines.
                        this.on("invalid" ,this.validationErrorHandler, this);

                        this.on("error", this.serverErrorHandler, this);

               In the first line we are catching the validation errors, by specifying the event name as "invalid", and the alternative function name as "this.validationErrorHandler" and the current context as "this". Here, whenever the validation error occurs, that falls under the event "invalid", it's being caught and bind with the method "this.validationErrorHandler". Similarly in the second line  the server error is being caught and bind with the method "this.serverErrorHandler". Server error can be, the external server errors. This particular context also covers the error/exception handling in backbone.js. 
  
     2. & 3. are the validation and server error handling methods respectively. Inside these methods, just printing the model used and error status.

     4. defaults method, this method is used to assign default values for the unassigned property/key. Note that we always post the request in the JSON format. This is in the form of key-value pair, in case we missed particular key-value pair, that can be assigned with the default value specified in the defaults method. In our example, for two keys("lname","age") we assigned default value as "Not Specified" for both. This is very useful, in working with huge data-applications.

    5. validate method, this method is used to validate the model attributes, before posting it to server. In our example, we validated that the "fname" shouldn't be null. If "fname" is null it should throw the error string, and it is being caught by the "validationErrorHandler" method as specified in the initialize method. These methods(defaults, validate....) are being called implicitly when we try to send the model-data to server. Here is the point the Backbone.JS excelled from all other Single Page Applications.


Feel the joy of having Backbone.JS from here....

    6. urlRoot attribute, itself provides a RESTful mechanism, ie., the urlRoot changes according to the server methods(post, get, put, delete). In our example, we specified a single url in the urlRoot attribute, which dynamically changes according to the request to the server. This is absolutely RESTful.

   7. sync method, this method is the precious part of backbone.js. Because, sync method allows us to sync our data before executing the server request. The model-data which we wants to be persisted in the app, can be stored locally in the app before posting it to the server. In detail we would be looking at this sync method during the execution of our application.

   8. parse method, this method gets executed immediately after the server response received. Inside this method, we can parse the response we got and manipulate it as we wanted.

            Let's we brace ourself to taste the flavours of backbone.js, Yes, now open the Netbeans IDE and press run button after pasting the above snippet in the right place as mentioned above. Once the home page has appeared, right click inside the web page and choose 'inspect element' option.
The firebug window is opened now. Choose the 'console' tab from the horizontal menu list.


POST :
  • Inside the console prompt, after the '>' mark type/copy the following command. The screen shot to indicate the console prompt is shown below. 


          > var newUser = new backboneApp.Models.MymodelModel({"fname" : "Suresh" , "lname" :"Kumar", "age" :"25"});
  • Press enter, next, type/copy the following command in the same console prompt. 
             > newUser.save();

          In a moment, you'd be getting the following response from the server. Have a look at the screen shot below.



         The console prompt results are numbered from 1 through 3.  And the explanation for each result is as follows:

  1. This is the log written by us inside the sync method. As I told you earlier, the sync method get called after the validation success and before the server request. So before calling the server the sync method is called. 
  2. The second line is to exhibit how dynamically the url is getting changed;  Yes, in the urlRoot attribute we just specified 'http://127.0.0.1:8080/employees'. And we haven't specify the method it is going to use. But note that in the console prompt, it is clearly indicating that the request is a 'POST' request and it's url is 'http://127.0.0.1:8080/employees' for now. What we did is just calling the variable newUser's save method. This save method is provided by backbone.js, to implicitly send/save the model-data to the server. And please note that the variable newUser is simulated with the model 'backboneApp.Models.MymodelModel()'. ie., the variable newUser act as an instance of backboneApp.Models.MymodelModel() since we instantiated it by the line below.                                                                                                                                                   var newUser = new backboneApp.Models.MymodelModel({"fname" : "Suresh" , "lname" :"Kumar", "age" :"25"});
  3. The parse method's log, in which once the server send the response, it is being captured by the parse method. We can do appropriate implementation over the response we got inside the parse method. In our example, we just printed the response that server sent, in our console log.
      Next, before see other server methods (get, put, delete). Let's have a look at the validate and defaults methods.
  • Now in the same console prompt, copy the following command
         > var newUser = new backboneApp.Models.MymodelModel({ "lname" : "Kumar", "age" : "25"});
  • Next copy the below command.
        > newUser.save();
        You'd be getting the following console log after the line newUser.save();

hello
validation error handled, model{"lname":"Kumar","age":"25"}error"First Name Shouldn't Be Null !!!"
false

         This log shows that, the model can't be pushed into the server, since it is throws validation error, and the message also be displayed. Find the error message is written in the method validate as follows.

         validate: function(attrs, options) 
        {           
                   if(attrs.fname == null )
                   {   
                            return "First Name Shouldn't Be Null !!!";
                   }           
         }
     In this function please note that, we've only validated the "fname" attribute, if the "fname" attribute is null(i.e., not specified in the model), throws an error message "First Name Shouldn't Be Null !!!". So it is caught by the validationErrorHandler method.  This is how validation is done in backbone.js implicitly.

Next, we're going to look at defaults method. For that copy the following command in the console prompt as earlier.

        > var newUser = new backboneApp.Models.MymodelModel({"fname":"Suresh","lname":"Kumar"});
press enter, then the next command as follows.
        > newUser.save();
press enter, you would be getting the following output log in the console prompt.


           From the above screenshot, the explanations for the numbered lines.

  1. Note that inside sync method, before posting it to server, the "age" attribute value is set as default value "Not Specified" since we left "age" attribute from our model. This happens because, we added in the defaults method, "age" attribute and it's default value. So, if "age" attribute is not specified/null, then default value would be assigned for that. Similarly for the "lname" we'd set the default value as "Not Specified".
  2. This is the response that we got from the server after posting into the server. Please note that in this line "_id" is the primary key generated by the MongoDB, and returned as a response. Through this "_id" only we're going to identify the record/model inside the MongoDB in server.


PUT :

            Next, we're going to look at PUT request from the client. For that, copy the "_id" attribute value from the previous response. Because, we're going to update the model through primary key MongoDB "_id" only. For me, the previous response's "_id" value is "53843085389c60e702000002". Now copy the following command in console prompt.
 
              > newUser.save({"id" : " 53843085389c60e702000002","fname" : "Ramesh" , "lname": "Kumar", "age" : "24"});

         press enter, in a moment you would be getting the following log from the console prompt. Have a look at the below screenshot.


  • Look at the indicated line from the screenshot. I've used the same newUser.save method with some attribute in it. Now it has been changed to PUT request, and the url is changed as the id 53843085389c60e702000002 suffixed one. ie., Now the url is "http://127.0.0.1:8080/employees/53843085389c60e702000002" here the id "53843085389c60e702000002" suffixed with the base url and the request method also changed to PUT, which is for updating particular record.
  • Look at the response line as well, it has changed with the updated model-data that we sent. You may wonder how the POST becomes PUT with the same save().  Now see how smartly the backbone.js library written, save() method is used for both POST and PUT request. But how it decides when to POST and when to PUT. Now recollect the POST request, there we called the save method without the model-attribute "id". But here in PUT request, we called the same save method with the model-attribute "id". So, the attribute "id" is the one causes, it is a POST or PUT.  If we specified "id" attribute in our model, then the request becomes PUT, if not specified then the request becomes POST this is for the method save(). 
                  
GET :

            Now the GET request, similarly copy the following command in your console prompt.
        > newUser.fetch( {"id":"53843085389c60e702000002"});
         
           press enter , you would be getting the following result in your console prompt. 


                   For GET request, you should be using fetch() method. The fetch() call and the GET url are indicated in the above screen shot. Since you called fetch() method with the model-attribute "id" it fetches only the corresponding "id" record. If you doesn't specify the "id" you would be fetching all the records from the server.  Find the screen shot below for fetching all the records from the MongoDB server. 


                   From the above screen shot the explanations for the numbered indications are as follows.
  1. Recreating the variable newUser, and simulating it with the backboneApp.Models.MymodelModel.
  2. Calling the fetch() method, with the variable/object newUser.
  3. Inside the parse method, printing the response, shows all the records from the server.
  4. The modified url according to the fetch() request.

DELETE :

                    For DELETE request, similar to other requests copy the below commands in the console prompt.
            
          > var newUser = new backboneApp.Models.MymodelModel({"id":"53843085389c60e702000002"});  
          
        press enter, then the next command is, 
     
          > newUser.destroy();
         press enter, In a moment you would be getting the following log in the screen shot below. 



              From the screenshot....
  1. Instantiating a backboneApp.Models.MymodelModel object/variable.
  2. calling destroy() method with the object/variable.
  3. RESTful change in the url for DELETE.
       In the url now the change is "http://127.0.0.1:8080/employees/53843085389c60e702000002" and the request is DELETE. This happens because, destroy() is the method to call DELETE request as shown above. That's it, this is how the server communication in backbone.js is achieved through model. 


Adding extra stuff to URL:

                                              Similar to 'urlRoot' attribute in model, there is another attribute called 'url'. Which is used to add some extra stuff(authentication token and stuff like that)  in your URL. But this 'url' attribute will not be RESTful as 'urlRoot'. If you use both 'url' and 'urlRoot' attributes, the 'url' attribute has the highest priority over 'urlRoot'.

Have a look at the below attribute/function.
        
              url: function(attrs, options)
              {
                     var newUrl = this.urlRoot;
                      if(this.isNew())
                     {
                      //If you have a customized url, and don't want to use the standard url for all. Then you
                      //can customize it here as you want.
                      //While returning you can add your authentication token kind of stuff along with the url.
                        return newUrl;
                      }
                    else
                    {
                         //Just like the above description, you can customize it as you want.
                         return newUrl+'/'+this.id;
                    }          
             },


Note: You can use the 'url' and 'urlRoot' both as a function but returning a string of url. If you add the above 'url' function inside your model, this 'url' function has the highest priority. ie., In every call this 'url' function get called, But this behaves the same as 'urlRoot' for now.


            This is how the backbone.js provides server communication in out of the box unlike rest of the Single Page Applications. Take this as a simple but detailed course of Backbone.js and start exploring the SPA with backbone.js. There are several functions that has been untold by me, what I've covered is a very fundamental course of backbone.js. You can explore more functions in backbone.js here. Please give your valid feedback on this course - Thanks :-)