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

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

It is time for the second part of the series on the LoopBack API framework.

Part 2: Working with relationships

In the first part of the LoopBack series we introduced the following aspects:

  • Installation requirements.
  • Create a new LoopBack project.
  • Define models to expose in the API.
  • Create custom methods to extend the default ones produced by LoopbBack.
  • Add extra model validation.

In this part we will learn how to work with custom validations and relationships. The rating system is going to become a survey system and therefore we will have to create a different model:

  • A survey exists to cover a topic.
  • A survey contains a list of resources which can be voted for.

At the end of this part we will have a system that will allow us to create simple surveys for groups of topics.

Updating the model

Introducing the Survey model

A survey is a collection of questions and answers, so at the end we will need to create a list of resources belonging to a topic and the votes associated with these resources.

survey

Property name Property type Required? Description
topic string Y Name of the survey, e.g. "food"

resource

Property name Property type Required? Description
name string Y Name of the resource, e.g. "apples"

Creating relations

A survey can contain several votes. A vote belongs to a single survey. For LoopBack we will have to create:

  • a belongsTo relationship from vote to survey,
  • a hasMany relationship from survey to vote.

In the same way, we need to create the relationships between the survey and the resource:

  • a belongsTo relationship from resource to survey,
  • a hasMany relationship from survey to resource.

To create relations in LoopBack, just use the command:

$ slc loopback:relation

Test the progress

You can run the application now and check the progress. Create for example a new survey with "topic" = "fruits" and start adding new votes. There is no need to implement a new method, the default findAll survey method will do the job if the following filter is passed:

{"include": "votes"}

This way you can get the entities related with the model; we can check a survey with the available resources like this:

{"include": "resources"}

If you want to recover all the related entities, you can pass an array of relations:

{"include": ["resources", "votes"]}

Fine-grained model definition

Now that we have created a survey and a resource model, we are on the way to developing a new application. The problem is that the resource is identified by a generated ID. By default, the LoopBack wizard creates the new model injecting a generated ID. This is usually the desired operation and it is the most common operation, but the wizard just helps to configure the model JSON definition.

The resource model does not have an identity by itself, the resources only can exists if there is a survey to work with. The ID of the model is indeed composed by the survey ID and the resource name. LoopBack allows composite IDs, but with the limitation that the GET REST service cannot handle composite IDs. This limitation can be circumvented by using the REST method findOne with the proper filters.

To configure the resource model to have a composite ID, we need to turn off the idInjection and define the IDs by hand. Here is a diff with the changes:

--- common/models/resource.json (date 1463829885000)
+++ common/models/resource.json (date 1463829916000)
@@ -1,14 +1,20 @@
 {
   "name": "resource",
   "base": "PersistedModel",
-  "idInjection": true,
+  "idInjection": false,
   "options": {
     "validateUpsert": true
   },
   "properties": {
     "name": {
       "type": "string",
-      "required": true
+      "required": true,
+      "id": true
+    },
+    "surveyId": {
+      "type": "number",
+      "required": true,
+      "id": true
     }
   },
   "relations": {

Test the progress

With these changes, we will be sure that no duplicate topics will exist for the same survey. When you try to generate two duplicate resources for the same survey, the server will fail with an error message indicating that the entity is duplicated.

Creating default resources

LoopBack has some useful features. One of them is to run all the JavaScript files that exist in the directory ./server/boot in alphabetical order. We are going to take advantage of this feature to generate some default data to work on;, this way we do not need to load the application with test data each time we restart the application (we are using in-memory storage).

First we need to create a new file that we will call script.js in directory ./server/boot. This file will run on start so we only need to require the different models to populate and start populating. As example we are going to create two surveys, one to evaluate the fruits and another for colors.

module.exports = function(app) {
  var Survey = app.models.Survey;
  var Resource = app.models.Resource;

  Survey.create([
    {"topic": "fruit"},
    {"topic": "color"}
  ], function (err, surveys) {
    if (err) throw err;

    Resource.create([
      {"name": "apple", "surveyId": surveys[0].id},
      {"name": "watermelon", "surveyId": surveys[0].id},
      {"name": "banana", "surveyId": surveys[0].id},
      {"name": "kiwi", "surveyId": surveys[0].id}
    ], function (err, fruits) {
      if (err) throw err;
    });

    Resource.create([
      {"name": "green", "surveyId": surveys[1].id},
      {"name": "yellow", "surveyId": surveys[1].id},
      {"name": "purple", "surveyId": surveys[1].id},
      {"name": "red", "surveyId": surveys[1].id}
    ], function (err, fruits) {
      if (err) throw err;
    })
  })

};

Stop and run the server again, the data should be available now.

curl -X GET --header "Accept: application/json" "http://localhost:3000/api/surveys?filter=%7B%22include%22%3A%20%22resources%22%7D"

That's all folks! In the next part we will finish creating a basic web page to consume the created REST service.

Read more