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

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

Welcome back to this tutorial series about fast prototyping a web application based on REST services. In the previous posts, we created a simple service to implement a survey service that allows to create surveys for certain topics and grouping resources to rate, see LoopBack.io tutorial part 1 and part 2.

This service is perfect for developers but it is intended that any user can provide his rates, and currently it is not very user friendly as the communication has to be performed in JSON directly to the server.

Part 3: Integrating with Angular 2

In the following article, we will explain how to integrate an Angular 2 based web application with the new REST service. We will not cover the Angular 2 development, just the LoopBack integration steps. Anyway, if you want to know more details about the Angular 2 integration you can ask in the comments.

Serving static content

Perhaps somebody wonders about the LoopBack directory called client in the base project. LoopBack can be used to serve any content too and therefore integrate the whole application development out-of-the-box. In our previous posts we used only the http://localhost:3000/explorer URL which leads to the StrongLoop API Explorer, a useful tool for testing and debugging our current REST API. If you try to visit the root path at http://localhost:3000, you will find something like this:

{"started":"2016-06-08T14:40:21.978Z","uptime":7.033}

Where does this information come from? As I said, LoopBack allows to serve any content, so it allows to define routes outside the API path. By default, LoopBack defines a default handler for the root path to return the server status. This route is created by the LoopBack generator in the server/boot/root.js file. Serving static content from client directory is indeed the default behaviour, so we only need to remove this file or rename it to root.js.bck for example. If you try again, you will get a 404 error page instead – we are on the right track!

Capturing the request IP

Our surveys service is based on IPs. We assume that users will have their own computer with a static IP. This it is not too realistic, but it is how we defined it, so ... how can we determine the request IP ? We can not know the IP on the client side as it is JavaScript based. We could create a new service to recover the request IP, but at the end we are including an extra request because all the calls to the Vote API will contain the IP as well.

To solve the problem we should think about how the users will access the Vote model:

  • A client can only check the votes it originates. We do not want that other clients can know what is our rate for the different resources.
  • A client can create votes only for the origin IP. We do not want that clients can change the IP value and therefore pollute our votes.
  • A client can read only the average votes for a topic. We want clients to have an average value of all votes for a determined resource.

We are going to learn how the operation hooks work for LoopBack and therefore how to handle the way we process the API calls.

Using operation hooks

The operation hooks allow us to intercept the REST calls in different scenarios. For our use case, we need to do reach the following goals:

  • Intercept the vote access calls and update the filter to include only votes that belong to the client IP.
  • Intercept the vote before saving and inject the client IP.
  • Allow users to get an average value of all the votes without IP restrictions.

The first task will be to put the request IP in the LoopBack context. The IP is not available in the model context, so we need a way to expose this value to the hooks. We will have to update the server.js code for the task. Add the following code to the file:

//http://stackoverflow.com/questions/35660857/loopback-get-ip-address-from-operation-hook
app.remotes().before('*.*', function(ctx,next) {
  loopback.getCurrentContext().set('remoteAddress',ctx.req.connection.remoteAddress);
  next();
});

app.remotes().before('*.prototype.*', function(ctx,instance,next) {
  loopback.getCurrentContext().set('remoteAddress',ctx.req.connection.remoteAddress);
  next();
});

This way, we store the attibute remoteAddress in the LoopBack context that will be recoverable in the model hooks. We did not update any vote yet, just leave everything ready for the integration. Previously we updated the Vote model to expose a new remote method voteAverage that allow us to retrieve the average value for a given topic. We need to update this file again to include the hooks so include the following code into it:

var loopback = require('loopback');
....

  Vote.observe('before save', function (ctx, next) {
    ctx.instance.ip = loopback.getCurrentContext().get('remoteAddress');
    next();
  });
  Vote.observe('access', function (ctx, next) {
    if (!loopback.getCurrentContext().get('skipVoteIpFilter')) {
      if (!ctx.query.where) {
        ctx.query.where = {};
      }
      ctx.query.where.ip = loopback.getCurrentContext().get('remoteAddress');
      console.log('Accessing %s for IP %s', ctx.Model.modelName, ctx.query.where.ip);
    }
    next();
  });

Now, we use the previously stored value to:

  • Inject the values to the Vote instance before saving (and before validation).
  • Add the client's IP to the filter in case of access, so we restrict the Votes to the client that requested them.

Pretty simple, right? We can do some tests and load some votes. You can use the explorer or a boot script as we saw in the previous part. Everything should work fine now but somehow, the voteAverage method is not working as expected, the hooks attatch before the model operations are called so at the end our new method it is only taking the votes from the same client.

If you are one of the people who wondered "Why did you add a check on access hook to skip the IP filter?", well the answer is because we will need to skip it when we use the voteAverage method. As we did before, we will use the LoopBack context. This context is renewed per request, so we will be sure that client votes or accesses do not mess up each other. Update the voteAverage method with the following code:

  Vote.voteAverage = function(resource, cb) {
    loopback.getCurrentContext().set('skipVoteIpFilter', true);
    Vote.find({where: {resource: resource}}, function (err, votes) {
      loopback.getCurrentContext().set('skipVoteIpFilter', false);
      var response = 0;
      votes.forEach(function (vote) {
        response += vote.value;
      });
      if (response) {
        response = response / votes.length;
      }
      cb(null, response);
    });
  };

Basically we set the value context to skip the filter before we trigger the access hook (Vote.find). This way we can pass the restriction and allow that all Votes count in the average value. You can test the method again to check that values are back to normal.

Generate Angular 2 service for our REST API

Loopback has some SDKs to easily consume the REST API. These SDKs support the following scenarios:

  • iOS SDK
  • Android SDK
  • Angular SDK
  • Use LoopBack in the client with browserify.

As you can read in the list of available SDKs, Angular 2 is not available yet but we can find a workaround. For this task we need to use a test branch for the LoopBack Angular SDK generator that includes the support for Angular 2.

Add or update the following devDependencies in the package.json file:

  "devDependencies": {
    "loopback-sdk-angular": "github:qeti/loopback-sdk-angular#188-angular2-support",
    "loopback-sdk-angular-cli": "github:qeti/loopback-sdk-angular-cli#37-angular2-support",
    ...
  }

We are almost done, we need to update the libraries to reflect the change in package.json and then perform the services generation.

$ npm install
$ ./node_modules/.bin/lb-ng ./server/server.js ./client/app/lb-services.ts -l angular2

We should have a new file ./client/app/lb-services.ts with the API client generated to be used directly with Angular 2.

That's all, folks! If we change the API we can just regenerate the services.

Download the code from GitHub

The LoopBack project with the Angular 2 frontend can be found at GitHub. You can review, test or fork it to your own purposes.

You can use the comments to ask questions or leave a feedback, we will be happy to answer all of them. Thanks for your attention; we hope that this series of tutorials helps you.

Read more