Defining and Mapping Data Relations with LoopBack Connected Models

In LoopBack, working with data is usually the core of business logic. We typically start by defining models to represent business data. Then we attach models to configured data sources to receive behaviors provided by various connectors. For example, connectors for databases implement methods for models to perform create, read, update, and delete (CRUD) operations. Furthermore, you can expose models and their methods as REST APIs so client applications can easily consume them.

Individual models are easy to understand and work with. But in reality, models are often connected or related. The moment you start to build a real-world application with multiple models, relations between models will come into the picture naturally. For example:

A customer has many orders and each order is owned by a customer.

A user can be assigned to one or more roles and a role can have zero or more users.

A physician takes care of many patients through appointments. A patient can see many physicians too.

With the connected models, LoopBack can present data to client applications as a connected graph, with connectors to interact with the backend systems behind the scenes. The data graph is then exposed as a set of APIs for the client to not only interact with each of the model instances, but also “slice and dice” the information based on the client’s need.

The first part of the blog walks through different types of relations, how to define them, and what Node.js APIs are available for navigating and associating the connected models. The second part of the blog describes how to map data navigation and aggregation capabilities to REST APIs.

Understanding and defining relations

LoopBack model relations are inspired by Ruby on Rails Active Record Associations. You’ll find a lot of similarity in terms of concepts and how it works. At the moment, LoopBack supports the four types of relations:

belongsTo

hasMany

hasMany through

hasAndBelongsToMany

Let’s examine the relation types one by one. We’ll explain what a relation is for, how to declare it, and how to use it with the utility methods mixed into the model by LoopBack.

The belongsTo relation

A “belongsTo” relation specifies a one-to-one connection between two models: each instance of the declaring model “belongs to” one instance of the related model. For example, in an application with customers and orders, each order “belongs to” one customer, as illustrated in the diagram below.

The declaring model (Order) has a foreign key property that references the primary key property of the target model (Customer). If a primary key is not present, LoopBack will automatically add one.

Defining a belongsTo relation

A belongsTo relation has three configurable properties:

The target model, such as Customer.

The relation name, such as ‘customer’. It becomes a method on the declaring model’s prototype.

The foreign key property on the declaring model, such as ‘customerId’. It references the target model’s primary key (Customer.id).

You can declare relations in the options property in models.json, for example:

foreign key: The relation name appended with ‘Id’, for example, ‘customer’ ⇒ ‘customerId’

Methods added to the model for belongsTo

Once you define the belongsTo relation, a method with the relation name is added to the declaring model class’s prototype automatically, for example: Order.prototype.customer(...).

Depending on the arguments, the method can be used to get or set the owning model instance.

Function

Code snippet

Get the customer for the order asynchronously

order.customer(function(err, customer) { … });

Get the customer for the order synchronously

var customer = order.customer();

Set the customer for the order

order.customer(customer);

The hasMany relation

A “hasMany” relation indicates a one-to-many connection between two models. It indicates that each instance of the declaring model has zero or more instances of another model. This relation often occurs in conjunction with a belongsTo relation. For example, in an application with customers and orders, a customer can have many orders, as illustrated in the diagram below.

The target model Order has a property customerId as the foreign key to reference the declaring model (Customer) primary key id.

Defining a hasMany relation

A hasMany relation has three configurable properties:

The target model, such as Order

The relation name, such as ‘orders’. It becomes a method on the declaring model’s prototype

The foreign key property on the target model, such as ‘customerId’. It references the declaring model’s primary key (Customer.id)

You can declare relations in the options property in models.json, for example:

The hasManyThrough relation

A “hasManyThrough” relation creates a many-to-many connection with another model through a third model. This relation indicates that the declaring model matches zero or more instances of the related model through the third model. For example, in an application for a medical practice where patients make appointments to see physicians, the relevant relation declarations could look like this:

The “through” model, Appointment, has two foreign key properties, physicianId and patientId, that reference the primary keys in the declaring model, Physician, and the target model, Patient.

The hasAndBelongsToMany relation

A hasAndBelongsToMany relation creates a direct many-to-many connection with another model, with no intervening model. For example, in an application with assemblies and parts, where each assembly has many parts and each part appears in many assemblies, you could declare the models this way:

Slicing and dicing the data graph

Now we have covered the four types of model relations in LoopBack. As you have seen, a relation defines the connection between two models by connecting a foreign key property to a primary key property. For each relation type, a list of helper methods are mixed into the model class to help navigate and associate the model instances to load or build a data graph.

Often, client applications want to pick and choose the relevant data from the graph using APIs, for example to get user information and recently-placed orders. LoopBack provides a few ways to express such requirements in queries.

Inclusion

To include related models in the response for a query, we can use the ‘include’ property of the query object or use the include method on the model class.

The ‘include’ can be a string, an array, or an object. The valid formats are illustrated below using examples:

Load all user posts with only one additional request.

User.find({include:'posts'},function(){...});

It is the same as:

User.find({include:['posts']},function(){...});

Load all user posts and orders with two additional requests.

User.find({include:['posts','orders']},function(){...});

Load all post owners (users), and all orders of each owner

Post.find({include:{owner:‘orders’}},function(){...});

Load all post owners (users), and all friends and orders of each owner

Post.find({include:{owner:[‘freinds’,‘orders’]}},function(){...});

Load all post owners (users), and all posts and orders of each owner. The posts also include images.