Sunday, June 16, 2013

Lesson 5: Angularjs Tutorial: Backend - Node Express Mongodb Angularjs integration ($http, deferred promises with $q, $resource)

In Lesson 4 we wrapped up our device management CRUD in angularjs with both unit tests and end to end tests. We also organized our project structure with angularjs seed. So far, we have been using static data stored in an array within our Devices Service. In this lesson we will create a REST based Node/MongoDB backend which can persist our data.

Here are the stories we will work in Part-1 (Mongo integration will be Part-2):

  1. Create an node-express server application which will return the list of devices in JSON format (Read)
  2. Server returns a single device info given an ID
  3. Server updates devices information
  4. Server deletes a particular device 
  5. Modify the angularjs application Service module to communicate with the server

Create an node-express server application which will return the list of devices in JSON format (Read) & which returns a single device information

  • cd ~/angular/cotd
  • mkdir server
  • Create a file called package.json in the server folder to define our dependencies
{
  "name": "cotd-server",
  "description": "cotd backend",
  "version": "0.0.1",
  "private": "true",
  "dependencies": {
    "express": "3.2.2"
  }
}
  • npm install -- this will install express in the node_modules directory
Lets create the server application using node & express. We will create a GET route which will return a list of devices. 

Code for server.js which returns a list of devices:


Run using node server.js; Test by going to localhost:3000/devices

Lets refactor our server code into node modules, which will help us later as the project becomes more complex. Create a sub-folder called routes & a new file called devices.js

Code for module devices.js


Refactored server.js

Add Device

We add a device using the POST method to send the JSON data. Add the following route to server:

In Server.js add:

app.configure(function () {
    app.use(express.bodyParser());
});

app.post('/devices', devices.add);          // add new device

In the devices module (devices.js):

exports.add = function(req,res){
    var dev = req.body;
    devices.push(dev);
    console.log(devices);
    res.send([{status: '1'}]);
};

You can test this using curl in the terminal or by using a chrome extension called POSTMAN:

curl -i -X POST -H 'Contet-Type: application/json' -d '{"id":7, "name": "iphone", "assetTag":"a23456", "owner":"dev", "desc":"iOS4.2"}' http://localhost:3000/devices

Implement update & deletion of a particular device 

Deleting should be simple to achieve. It involves removing the JSON object from our devices array. Using the javascript array's filter api is fairly easy & straightforward. (Note: the filter is newer ecma standard and is not supported ie-8 and below, although the API document provides a work-around if you need it)

server.js - add delete route


app.delete('/devices/:id', devices.delete); // delete

devices.js -- node module

exports.delete = function(req, res){
    var id = req.params.id;
    devices = devices.filter(function(item){
        return item.id != id;
    });
    console.log("DELETED \n");
    console.log(devices);
    res.send([{status: '1'}]);
};

TEST: curl -i -X DELETE -H 'Conent-Type: application/json' http://localhost:3000/devices/6

Updating is also fairly straightforward to achieve in a similar manner, add the following in server.js
app.put('/devices/:id', devices.update);    // update

devices.js
exports.update = function(req, res){
    // get the device
    var id = req.params.id;
    var dev = req.body;
    if(id != dev.id){
        console.log("id's do not match for update");
        res.send([{status: '0'}]);
    }
    console.log(dev);

    // find the selected device & update
    devices.forEach(function(item, i){
        if(item.id == id){
            // update           
            devices[i] = dev; 
            res.send([{status: '1'}]);
            return;
        }
    });
    res.send([{status: '0'}]);
};

TEST: curl -i -XPUT -H 'Content-Type: application/json' -d '{"id":6, "name": "testupdateiphone", "assetTag":"a23456", "owner":"dev", "desc":"iOS4.2"}' http://localhost:3000/devices/6

Lets check how we are doing with our stories: (git checkout v1.4.1)


  1. DONE - Create an node-express server application which will return the list of devices in JSON format (Read)
  2. DONE - Server returns a single device info given an ID
  3. DONE - Server updates devices information
  4. DONE - Server deletes a particular device 
  5. Modify the angularjs application Service module to communicate with the server
  6. Modify the unit & e2e test cases to use Mock data 

Modify the angularjs application Service module to communicate with the server using $http & $ngResource

Angularjs provides the $http service which provides a pretty comprehensive API to handle HTTP requests. More info can be found here. Lets modify our angular app service to get the data from the express backend server we created above.

Add this to app/service.js file. This just makes the HTTP call for now.

    items.query = function(){
        var promise = $http.get('http://localhost:3000/devices')
        .success(function(d){
            console.log(d);
        })
        .error(function(d, status){
            console.log("error getting data from server: " + status);
        });
    }

We will get the following common error on certain strict browsers like chrome:

XMLHttpRequest cannot load http://localhost:3000/devices. Origin http://localhost:8000 is not allowed by Access-Control-Allow-Origin.

CORS Background

This is because for security browsers do not allow request to a different domain, even though it is the same domain but a different port. The browsers are enforcing the same origin policy. CORS (Cross Origin Resource Sharing) "The CORS standard works by adding new HTTP headers that allow servers to serve resources to permitted origin domains." There are many options to handle same origin policy & CORS is one way to do it. The other is to configure a reverse proxy.

To make our client side app be able to call the backend REST server the CORS header must be present which will tell the browser that this request is in good order. http://enable-cors.org/index.html

The following HTTP header needs to be added by the server to its response.

Access-Control-Allow-Origin: *

CORS References:
http://www.html5rocks.com/en/tutorials/cors/
http://stackoverflow.com/questions/7067966/how-to-allow-cors-in-express-nodejs

We will be using the node-cors package which will allow us to configure and handle the CORS header in our express server application. node-CORS is a node.js package for providing a connect/express middleware that can be used to enable CORS with various options.

Add the following in server/package.json:
"dependencies": {
    "express": "3.2.2",
    "cors": "*"
  }

Modify the server to utilize cors package (server.js):


var express = require('express'),
    cors = require('cors'),
    devices = require('./routes/devices.js');

var app = express();

app.configure(function () {
    app.use(express.bodyParser());
    app.use(cors());
});

Lets modify the query method in our service to utilize deferred promises to get data from the backend. Here is the code which does that:


    items.query = function(){
        var deferred = $q.defer();

        var url = "http://localhost:3000/devices";

        $http.get(url).success(function(data, status){
            deferred.resolve(data);  
        })
        .error(function(data, status){
            console.log("error getting data from server: " + status);
            deferred.reject(data);
        });

        return deferred.promise;
    }


Test it using http://localhost:8000/app/index.html#/ & the device list should be working at this point.

Using ngResource ($resource)

Instead of utilizing the lower level $http service, angular provides the $resource abstraction for interacting with JSON REST services. Lets use that to see how it helps alleviate all the boilerplate code we have to write if we have to use the lower level $http service. 

ngResource API documentation provides details around the usage of this angular service. To use this we need to include the angular-resource.js within our app.

index.html (add the following)


  script src="lib/angular/angular-resource.js"


app.js (add to include the dependency)

angular.module('cotd', ['cotd.filters', 'cotd.services', 
                        'cotd.directives', 'cotd.controllers', 'ngResource']).

Using ngResource simplifies our service module significantly, since it provides a wrapper around the common REST functionality. Query, Save, Delete & Update functionalities are provided out of the box. We can replace all our previous code with 3 lines like so:

services.js (modify to include $resource)


 .factory('Devices', ['$resource', function($resource){
    return $resource('http://localhost\\:3000/devices/:deviceId',
        {},
        {update: {method:'PUT'}, isArray:false}
        );
  }]);

This resource definition gives the ability to interact with the RESTful server-side data source.

Within the controllers, you can call either the Devices "class" methods or the "instance" methods. The structure is typically:

Resource.query({params}, function success(){}, function error(){});

Refactored controller code is shown below, which performs all the CRUD operations:

--

--

Minor refactoring on the node server is shown here:

server.js

//Devices CRUD
app.get('/devices', devices.findAll);       // list
app.get('/devices/:id', devices.findById);  // find
app.post('/devices', devices.add);          // add new device
app.put('/devices/:id', devices.update);    // update
app.delete('/devices/:id', devices.delete); // delete

devices.js (module)
--

--

git checkout v1.5.0

2 comments:

Unknown said...

Music expresses that which cannot be said and on which it is impossible to be silent. See the link below for more info.


#expresses
www.ufgop.org

Madhu Bala said...


Today's Best article. It really helps me to enhance my skills.

AWS Training in Chennai
DevOps Training in Chennai
Java Training in Chennai
AngularJS Training in Chennai
German Classes in Chennai
German Language Classes in Chennai
German Language course in Chennai