LoopBack supports several kinds of embedded relations: embedsOne, embedsMany, embedsMany with belongsTo, and referencesMany.
Page Contents

Overview

LoopBack relations enable you to create connections between models and provide navigation/aggregation APIs to deal with a graph of model instances. In addition to the traditional ones, LoopBack also supports the following embedded relations:

  • EmbedsOne - a model that embeds another model; for example, a Customer embeds one billingAddress.
  • EmbedsMany - a model that embeds many instances of another model. For example, a Customer can have multiple email addresses and each email address is a complex object that contains label and address..
  • EmbedsMany with belongsTo - a model that embeds many links to related people, such as an author or a reader. 
  • ReferencesMany

EmbedsOne

EmbedsOne is used to represent a model that embeds another model, for example, a Customer embeds one billingAddress.

Sample embedded model

{
  id: 1,
  name: 'John Smith',
  billingAddress: {
    street: '123 Main St',
    city: 'San Jose',
    state: 'CA',
    zipCode: '95124'
  }
}

Define the relation in code

common/models/customer.js

Customer.embedsOne(Address, {
  as: 'address', // default to the relation name - address
  property: 'billingAddress' // default to addressItem
});

Parameters for the definition

  • methods - Scoped methods for the given relation
  • properties - Properties taken from the parent object
  • scope - Default scope
  • options - Options
  • default - Default value
  • property - Name of the property for the embedded item
  • as - Name of the relation

Options

  • forceId - force generation of id for embedded items, default to false
  • validate - denote if the embedded items should be validated, default to true
  • persistent - denote if the embedded items should be persisted, default to false

Define the relation in JSON

common/models/customer.json

{
  "name": "Customer",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string"
    }
  },
  "relations": {
    "address": {
      "type": "embedsOne",
      "model": "Address",
      "property": "billingAddress",
      "options": {
        "validate": true,
        "forceId": false
      }
    }
    ...
  }

Helper methods

  • customer.address()
  • customer.address.build()
  • customer.address.create()
  • customer.address.update()
  • customer.address.destroy()
  • customer.address.value()

For examples of these in use, see boot scripts in loopback-example-relations.

Operation hooks

You can define before save and after save operation hooks for an embedded model in an embedsOne relation. Then, updating or creating an instance of the container model will trigger the operation hook on the embedded model. When this occurs, ctx.isNewInstance is false, because only a new instance of the container model is created.

For example, if Customer embedsOne Address, and you define a before save hook on the Address model, creating a new Customer instance will trigger the operation hook.

EmbedsMany

Use an embedsMany relation to indicate that a model can embed many instances of another model. For example, a Customer can have multiple email addresses in an emailList property whose value is an array of objects; and each element in the array is an object with label and address properties.

Sample model instance with many embedded models

{
  id: 1,
  name: 'John Smith',
  emailList: [{
    label: 'work',
    address: 'john@xyz.com'
  }, {
    label: 'home',
    address: 'john@gmail.com'
  }]
}

Define the relation in code

common/models/customer.js

Customer.embedsMany(EmailAddress, {
  as: 'emails', // default to the relation name - emailAddresses
  property: 'emailList' // default to emailAddressItems
});

Parameters for the definition

  • methods - Scoped methods for the given relation
  • properties - Properties taken from the parent object
  • scope - Default scope
  • options - Options
  • default - Default value
  • property - Name of the property for the embedded item
  • as - Name of the relation

Options

  • forceId - force generation of id for embedded items, default to false
  • validate - denote if the embedded items should be validated, default to true
  • persistent - denote if the embedded items should be persisted, default to false

Define the relation in JSON

common/models/customer.json

{
  "name": "Customer",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string"
    }
  },
  "relations": {
    "emails": {
      "type": "embedsMany",
      "model": "EmailAddress",
      "property": "emailList",
      "options": {
        "validate": true,
        "forceId": false
      }
    }
    ...
  }

common/models/email.json

{
  "name": "EmailAddress",
  "base": "Model",
  "idInjection": true,
  "properties": {
    "id": {
      "type": "string",
      "id": true,
      "defaultFn": "uuid"
    }
  }

Helper methods

  • customer.emails()
  • customer.emails.create()
  • customer.emails.build()
  • customer.emails.findById()
  • customer.emails.destroyById()
  • customer.emails.updateById()
  • customer.emails.exists()
  • customer.emails.add()
  • customer.emails.remove()
  • customer.emails.get() - alias to findById
  • customer.emails.set() - alias to updateById
  • customer.emails.unset() - alias to destroyById
  • customer.emails.at()
  • customer.emails.value()

Operation hooks

You can define before save and after save operation hooks for an embedded model in an embedsMany relation. Then, updating or creating an instance of the container model will trigger the operation hook on the embedded model. When this occurs, ctx.isNewInstance is false, because only a new instance of the container model is created.

For example, if Customer embedsMany EmailAddress, and you define a before save hook on the EmailAddress model, creating a new Customer instance will trigger the operation hook.

EmbedsMany with belongsTo

Use an embedsMany with belongsTo relation to indicate a model that can embed many links to other models; for example a book model that embeds many links to related people, such as an author or a reader. Each link belongs to a person and it's polymorphic, since a person can be an Author or a Reader.

Example embedsMany with belongsTo model instance

{ 
  id: 1
  name: 'Book 1',
  links: [{
    notes: 'Note 1',
    id: 1,
    linkedId: 1,
    linkedType: 'Author',
    name: 'Author 1'
  }, {
  notes: 'Note 2',
    id: 2,
    linkedId: 1,
    linkedType: 'Reader',
    name: 'Reader 1'
  }]
}

Define the embedsMany relation for Book

common/models/book.json

{
  "name": "Book",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string"
    }
  },
  "validations": [],
  "relations": {
    "people": {
      "type": "embedsMany",
      "model": "Link",
      "scope": {
        "include": "linked"
      }
    }
  },
  "acls": [],
  "methods": []
}

common/models/link.json

{
  "name": "Link",
  "base": "Model",
  "idInjection": true,
  "properties": {
    "id": {
      "type": "number",
      "id": true
    },
    "name": {
      "type": "string"
    },
    "notes": {
      "type": "string"
    }
  },
  "validations": [],
  "relations": {
    "linked": {
      "type": "belongsTo",
      "model": "Person",
      "polymorphic": {
        "idType": "number"
      },
      "properties": {
        "name": "name"
      },
      "options": {
        "invertProperties": true
      }
    }
  },
  "acls": [],
  "methods": []
}

ReferencesMany

A ReferencesMany relation embeds an array of foreign keys to reference other objects. For example:

Sample referencesMany model instance

{
  id: 1,
  name: 'John Smith',
  accounts: [
    "saving-01", "checking-01",
  ]
}

Parameters for the definition

  • methods - Scoped methods for the given relation
  • properties - Properties taken from the parent object
  • foreignKey - Camel case of the declaring model name appended with Id
  • scope - Default scope
  • options - Options
  • default - Default value
  • as - Name of the relation

Options

  • forceId - force generation of id for embedded items, default to false
  • validate - denote if the embedded items should be validated, default to true
  • persistent - denote if the embedded items should be persisted, default to false

Define the relation in code

common/models/customer.json

{
  "name": "Customer",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string"
    }
  },
  "relations": {
    "accounts": {
      "type": "referencesMany",
      "model": "Account",
      "foreignKey": "accountIds",
      "options": {
        "validate": true,
        "forceId": false
      }
    }
...
}

Helper methods

  • customer.accounts()
  • customer.accounts.create()
  • customer.accounts.build()
  • customer.accounts.findById()
  • customer.accounts.destroy()
  • customer.accounts.updateById()
  • customer.accounts.exists()
  • customer.accounts.add()
  • customer.accounts.remove()
  • customer.accounts.at()

Transient versus persistent for the embedded model

Define a transient data source

server/datasources.json

{
  ...
  "transient": {
    "name": "transient",
    "connector": "transient"
  }
}

Use the transient data source for embedded models

server/model-config.json

{
  ...
  "Customer": {
    "dataSource": "db",
    "public": true
  },
  "Address": {
    "dataSource": "transient",
    "public": false
  },
  "EmailAddress": {
    "dataSource": "transient",
    "public": false
  },
  "Account": {
    "dataSource": "db",
    "public": false
  }
}
Tags: models