Loopback: include model relations in your api responses

Loopback is solid choice for rapid node.js API development; you get User management and token authentication out of the box, a swagger api explorer, as well as a handy cli for creating models and relations.

However, I find the documentation to be quite dismal and sometimes it takes five open tabs from different resources to figure out something quite trivial.

One frustration in particular was adding nested/embedded relations to queries. For example, when I call /api/articles I want it to return the articles array as well as the embedded comments for each article. Again, finding this in the docs was a roundabout experience, so the point of this post is clarify how to do it in a clear and concise manner.

Here are some handy tricks I learned while developing an api recently:

Setting up the models

Lets say we have an article model. And as our app grows, we want to add comments to the articles and likes to the comments.

article.json

Our article model has a title and a hasMany relation to the comment model:

{
  "name": "article",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "title": {
      "type": "string",
      "required": true
    }
  },

  "validations": [],
  "relations": {
    "comments": {
      "type": "hasMany",
      "model": "comment",
      "foreignKey": ""
    }
  },
  "acls": [],
  "methods": {}
}
comment.json

Our comment model has a value and a belongsTo relation to the user model, as well as a belongsTo relation to the article model:

Major gotcha: make sure to create your own "user" model using the cli that extends the base User model. It is undocumented outside of this example and a bit of a mystery but once I did that it was smooth sailing.

{
  "name": "comment",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "value": {
      "type": "string",
      "required": true
    }
  },
  "validations": [],
  "relations": {
    "user": {
      "type": "belongsTo",
      "model": "user",
      "foreignKey": "",
      "through": ""
    },
    "likes": {
      "type": "hasMany",
      "model": "like",
      "foreignKey": "",
      "through": ""
    },
    "article": {
      "type": "belongsTo",
      "model": "article",
      "foreignKey": "",
      "through": ""
    }
  },
  "acls": [],
  "methods": {}
}


Using REST queries to fetch related models

For the sake of performance, you might not always want to embed the related models as the app gets bigger and you get more data. Loopback has a handy query language that lets you perform queries by passing "filters" to REST api calls.

The following assumes you have some seed data, and posted some comments to the articles and likes to the comments

To fetch all articles with embedded comments you can hit the api with the filter object passed in with the url like so:
${ROOT_URL}/api/articles?filter={"include":"comments"}

You can test it in the api explorer by pasting {"include":"comments"} into the filter field:
api explorer

You can further build the response by querying the likes associated with the comments as well by making the include filter an array and passing in "user" and "likes":
${ROOT_URL}/api/articles/${id}/comments?filter={"include":["user", "likes"]}

explorer 2


Using "scope" in the model definition

An alternative to REST queries is to use a "scope" object that references the related model in your model.json file. The difference here is that this will return whichever relation you define by default instead having to pass in the filter query manually.

article.json with "scope"
{
  "name": "article",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "title": {
      "type": "string",
      "required": true
    }
  },
  "scope": {
    "include": "comments"   // <--- embed all comments for each article
  },
  "validations": [],
  "relations": {
    "comments": {
      "type": "hasMany",
      "model": "comment",
      "foreignKey": "",
      "scope": {
        "include": "likes"    // <--- embed all likes inside each comment object
      }
    }
  },
  "acls": [],
  "methods": {}
}
comment.json with "scope"
{
  "name": "comment",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "value": {
      "type": "string",
      "required": true
    }
  },
  "validations": [],
  "scope": {
    "include": "likes"  // <--- embed all likes for each comment
  },
  "relations": {
    "user": {
      "type": "belongsTo",
      "model": "user",
      "foreignKey": "",
      "through": ""
    },
    "likes": {
      "type": "hasMany",
      "model": "like",
      "foreignKey": "",
      "through": "",
      "scope": {
        "include": "user"  // <--- embed user for each like
      }
    },
    "article": {
      "type": "belongsTo",
      "model": "article",
      "foreignKey": "",
      "through": ""
    }
  },
  "acls": [],
  "methods": {}
}

I hope this helps to clear up any confusion regarding fetching related models using the loopback framework. For me, this post will serve as permanent note for future projects.