Loopback.io: A good solution for rapid prototyping (part 1)

by Javier Puerto
Tags: LoopBack , Angular , Tutorial , Open Source , Web , REST , API

The web has changed a lot since 5 years ago. We as developers have noticed a big change in the way that we create web pages, long gone are the times when pages made with well-known IDEs and JavaScript were sporadically used for small tasks. Then CSS, AJAX, HTML5, … and many other standards were created – the era of the web based systems arrived!

Now we can really create web based applications that serve to anyone who has a browser installed to use them. But as to all good stories, there is a bad side, the complexity of developing for the web grew dramatically. We can continue applying the patterns that we used for years for other developments.

REST (representational state transfer) is an architectural style that perfectly fits the combination of JSON (JavaScript Object Notation) in the web based software paradigm. In this tutorial we will introduce you to this world by creating a quick web application based on Loopback.io.

Requirements

Installation

Follow the Node.js link to find instructions on the installation of Node.js on your system. To verify that it was correctly intalled, run the following commands, the version number depends on your installation:

$ node --version
v5.11.1
$ npm --version
3.9.0

Once we have verified that Node.js and npm has been correctly installed we can install LoopBack.

$ sudo npm install -g strongloop

Part 1: Create a simple API

Creating a rating API

For this tutorial we are going to create a simple rating service. The REST API will allow the users to create votes and know the average value from all the votes values about a topic.

Create a new strongloop project

First of all we need to create a new project:

$ mkdir rating-becompany-demo
$ cd rating-becompany-demo
$ slc loopback

Models

The models for our first tutorial part are quite simple, we will dive into the model entities relationship in next chapters. LoopBack allows us to create the models by using a simple assistant or by hand. For the sake of simplicity we will use the assistant to create a model:

slc loopback:model

Vote

Property name Property type Required? Description
ip string Y The IP of the user to vote.
value number Y The rating value for the resource.
created date Y Date of vote creation.
resource string Y Resource target of the rating.

Run the example

That is all folks, now we are ready to run the example:

$ node .
Web server listening at: http://0.0.0.0:3000
Browse your REST API at http://0.0.0.0:3000/explorer

You can now check the new model and methods aumatically created here.

Creating a vote

The IP 192.168.1.50 likes the apples so the user added a rating of 5.

$ curl -X POST --header "Content-Type: application/json" --header "Accept: application/json" -d "{
  \"ip\": \"192.168.1.50\",
  \"value\": 5,
  \"created\": \"2016-05-17\",
  \"resource\": \"apples\"
}" "http://0.0.0.0:3000/api/votes"

This results in the following response:

{"ip":"192.168.1.51","value":5,"created":"2016-05-17T00:00:00.000Z","resource":"apples","id":1}

Adding some restrictions

We have shown how to create a simple API to rate resources but we need to add some restrictions. * The same IP should not be allowed to rate a resource more than once. * The rate value should be restricted between 0 and 5.

Do not allow the same IP to vote for the same resource twice

We want to return status code 40x in that case.

Please note that this is only for educational purposes, for real it is better to add a constraint in database.

Loopback has some built-in validation features, otherwise custom validators can be created. See https://docs.strongloop.com/display/public/LB/Validating+model+data.

Update the file common/models/vote.js:


module.exports = function(Vote) {
  Vote.validatesUniquenessOf('ip', {scopedTo: ['resource']});
};

Now run the application and try to vote for the same resource twice with the same IP address. You should get a response like the following:

{
  "error":{
    "name":"ValidationError",
    "status":422,
    "message":"The `vote` instance is not valid. Details: `ip` is not unique (value: \"192.168.1.50\").",
    "statusCode":422,
    "details":{
      "context":"vote",
      "codes":{
        "ip":[
          "uniqueness"
        ]
      },
      "messages":{
        "ip":[
          "is not unique"
        ]
      }
    }
  }
}

Rating value should be in range [0,5]

We want the rate values to be restricted to the following values: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]. The reason is that we should keep the same scale so we can compare the different resources' rates.

As we saw previously, we can use a built-in validation method validatesInclusionOf by editing common/models/vote.js

module.exports = function(Vote) {
  Vote.validatesUniquenessOf('ip', {scopedTo: ['resource']});
  Vote.validatesInclusionOf('value', {in: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]});
};

You can now try to publish a vote with a value not in the list, the REST API will return an error indicating that the value is not in the authorized list.

Adding a custom method

Now we can store rates from the users but we somehow need to know what is the average rating for a resource. Do people like apples, or kiwis? Here comes a custom method to perform the aggregation.

Get the average vote value per resource

As we are using the memory datasource, no direct SQL or other features are available to perform the aggregate function.

We are going to create a new method "voteAverage" that calculates the average votes values per resource.

We need to update file common/models/vote.js:

module.exports = function(Vote) {
  Vote.validatesUniquenessOf('ip', {scopedTo: ['resource']});
  Vote.validatesInclusionOf('value', {in: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]});

  Vote.voteAverage = function(resource, cb) {
    Vote.find({where: {resource: resource}}, function (err, votes) {
      var response = 0;
      votes.forEach(function (vote) {
        response += vote.value;
      });
      if (response) {
        response = response / votes.length;
      }
      cb(null, response);
    });
  };
  Vote.remoteMethod(
    'voteAverage',
    {
      http: {path: '/voteAverage', verb: 'get'},
      accepts: {arg: 'resource', type: 'string', http: {source :'query'}},
      returns: {arg: 'voteAverage', type: 'numeric'}
    }
  );
};

Here we defined:

  • The method "voteAverage" for the Vote model. The method just retrieves all the votes for a given resource and performs an arithmetic media of all the values.
  • Expose the method in the path /voteAverage using the HTTP GET method.
  • The method accepts an argument resource of type string in the query.
  • The method returns a voteAverage of type numeric.

We can call the new method:

$ curl -X GET --header "Accept: application/json" "http://0.0.0.0:3000/api/votes/voteAverage?resource=apples"

And now the response will be something like:

{"voteAverage":4.5}

Conclusions

We have learnt how to create a simple REST API in a few steps. LoopBack generates the REST method and handles the communication, we have to take care of defining the model and sometimes the business logic. That makes Loopback a good candidate for quick prototyping of web based systems using a REST API.

Read more