Overview

Note:

This relation best works with databases that support foreign keyconstraints (SQL).Using this relation with NoSQL databases will result in unexpected behavior,such as the ability to create a relation with a model that does not exist. We are working on a solution to better handle this. It is fine to use this relation with NoSQL databases for purposes such as navigatingrelated models, where the referential integrity is not critical.

A belongsTo relation denotes a many-to-one connection of a model to anothermodel through referential integrity. The referential integrity is enforced by aforeign key constraint on the source model which usually references a primarykey on the target model. This relation indicates that each instance of thedeclaring or source model belongs to exactly one instance of the target model.For example, in an application with customers and orders, an order alwaysbelongs to exactly one customer as illustrated in the diagram below.

belongsTo relation illustration

The diagram shows the declaring (source) model Order has propertycustomerId as the foreign key to reference the target model Customer’sprimary key id.

To add a belongsTo relation to your LoopBack application and expose itsrelated routes, you need to perform the following steps:

  • Add a property to your source model to define the foreign key.
  • Modify the source model repository class to provide an accessor function forobtaining the target model instance.
  • Call the accessor function in your controller methods.

Defining a belongsTo Relation

This section describes how to define a belongsTo relation at the model levelusing the @belongsTo decorator to define the constraining foreign key.

/src/models/order.model.ts

  1. import {belongsTo, Entity, model, property} from '@loopback/repository';
  2. import {Customer} from './customer.model';
  3. @model()
  4. export class Order extends Entity {
  5. @property({
  6. type: 'number',
  7. id: true,
  8. })
  9. id: number;
  10. @belongsTo(() => Customer)
  11. customerId: number;
  12. @property({type: 'number'})
  13. quantity: number;
  14. constructor(data: Partial<Order>) {
  15. super(data);
  16. }
  17. }
  18. export interface OrderRelations {
  19. // describe navigational properties here
  20. }
  21. export type OrderWithRelations = Order & OrderRelations;

The definition of the belongsTo relation is inferred by using the @belongsTodecorator. The decorator takes in a function resolving the target model classconstructor and designates the relation type. It also calls property() toensure that the decorated property is correctly defined.

A usage of the decorator with a custom primary key of the target model for theabove example is as follows:

  1. class Order extends Entity {
  2. // constructor, properties, etc.
  3. @belongsTo(() => Customer, {keyTo: 'pk'})
  4. customerId: number;
  5. }
  6. export interface OrderRelations {
  7. customer?: CustomerWithRelations;
  8. }

Configuring a belongsTo relation

The configuration and resolution of a belongsTo relation takes place at therepository level. Once belongsTo relation is defined on the source model, thenthere are a couple of steps involved to configure it and use it. On the sourcerepository, the following are required:

  • In the constructor of your source repository class, useDependency Injection to receive a getter functionfor obtaining an instance of the target repository. Note: We need a getterfunction, accepting a string repository name instead of a repositoryconstructor, or a repository instance, in order to break a cyclic dependencybetween a repository with a belongsTo relation and a repository with thematching hasMany relation.
  • Declare a property with the factory function typeBelongsToAccessor<targetModel, typeof sourceModel.prototype.id> on thesource repository class.
  • call the createBelongsToAccessorFor function in the constructor of thesource repository class with the relation name (decorated relation property onthe source model) and target repository instance and assign it the propertymentioned above.The following code snippet shows how it would look like:

/src/repositories/order.repository.ts

  1. import {Getter, inject} from '@loopback/context';
  2. import {
  3. BelongsToAccessor,
  4. DefaultCrudRepository,
  5. juggler,
  6. repository,
  7. } from '@loopback/repository';
  8. import {Customer, Order, OrderRelations} from '../models';
  9. import {CustomerRepository} from '../repositories';
  10. export class OrderRepository extends DefaultCrudRepository<
  11. Order,
  12. typeof Order.prototype.id,
  13. OrderRelations
  14. > {
  15. public readonly customer: BelongsToAccessor<
  16. Customer,
  17. typeof Order.prototype.id
  18. >;
  19. constructor(
  20. @inject('datasources.db') protected db: juggler.DataSource,
  21. @repository.getter('CustomerRepository')
  22. customerRepositoryGetter: Getter<CustomerRepository>,
  23. ) {
  24. super(Order, db);
  25. this.customer = this.createBelongsToAccessorFor(
  26. 'customer',
  27. customerRepositoryGetter,
  28. );
  29. }
  30. }

BelongsToAccessor is a function accepting the primary key (id) of a sourcemodel instance (e.g. order.id) and returning back the related target modelinstance (e.g. a Customer the order belongs to). See alsoAPI Docs

Using BelongsToAccessor in a controller

The same pattern used for ordinary repositories to expose their CRUD APIs viacontroller methods is employed for belongsTo relation too. Once the belongsTorelation has been defined and configured, a new controller method can expose theaccessor API as a new endpoint.

src/controllers/order.controller.ts

  1. import {repository} from '@loopback/repository';
  2. import {get} from '@loopback/rest';
  3. import {Customer, Order} from '../models/';
  4. import {OrderRepository} from '../repositories/';
  5. export class OrderController {
  6. constructor(
  7. @repository(OrderRepository) protected orderRepository: OrderRepository,
  8. ) {}
  9. // (skipping regular CRUD methods for Order)
  10. @get('/orders/{id}/customer')
  11. async getCustomer(
  12. @param.path.number('id') orderId: typeof Order.prototype.id,
  13. ): Promise<Customer> {
  14. return this.orderRepository.customer(orderId);
  15. }
  16. }

In LoopBack 3, the REST APIs for relations were exposed using static methodswith the name following the pattern {methodName}{relationName} (e.g.Order.get__customer). While we recommend to create a new controller for eachhasMany relation in LoopBack 4, we also think it’s best to use the main CRUDcontroller as the place where to explose belongsTo API.

Handling recursive relations

Given an e-commerce system has many Category, each Category may have severalsub-categories, and may belong to 1 parent-category.

  1. export class Category extends Entity {
  2. @property({
  3. type: 'number',
  4. id: true,
  5. generated: true,
  6. })
  7. id?: number;
  8. @hasMany(() => Category, {keyTo: 'parentId'})
  9. categories?: Category[];
  10. @belongsTo(() => Category)
  11. parentId?: number;
  12. constructor(data?: Partial<Category>) {
  13. super(data);
  14. }
  15. }
  16. export interface CategoryRelations {
  17. categories?: CategoryWithRelations[];
  18. parent?: CategoryWithRelations;
  19. }
  20. export type CategoryWithRelations = Category & CategoryRelations;

The CategoryRepository must be declared like below

  1. export class CategoryRepository extends DefaultCrudRepository<
  2. Category,
  3. typeof Category.prototype.id,
  4. CategoryRelations
  5. > {
  6. public readonly parent: BelongsToAccessor<
  7. Category,
  8. typeof Category.prototype.id
  9. >;
  10. public readonly categories: HasManyRepositoryFactory<
  11. Category,
  12. typeof Category.prototype.id
  13. >;
  14. constructor(@inject('datasources.db') dataSource: DbDataSource) {
  15. super(Category, dataSource);
  16. this.categories = this.createHasManyRepositoryFactoryFor(
  17. 'categories',
  18. Getter.fromValue(this),
  19. );
  20. this.parent = this.createBelongsToAccessorFor(
  21. 'parent',
  22. Getter.fromValue(this),
  23. ); // for recursive relationship
  24. }
  25. }

DO NOT declare@repository.getter(CategoryRepository) protected categoryRepositoryGetter: Getter<CategoryRepository>on constructor to avoid “Circular dependency” error (seeissue #2118)