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 navigating related models, where the referential integrity is not critical.
A relation denotes a one-to-many connection of a model to anothermodel through referential integrity. The referential integrity is enforced by aforeign key constraint on the target model which usually references a primarykey on the source model. This relation indicates that each instance of thedeclaring or source model has zero or more instances of the target model. Forexample, in an application with customers and orders, a customer can have manyorders as illustrated in the diagram below.
The diagram shows target model Order has property customerId as theforeign key to reference the declaring model Customer’s primary key id.
To add a hasMany
relation to your LoopBack application and expose its relatedroutes, you need to perform the following steps:
- Add a property to your model to access related model instances.
- Add a foreign key property in the target model referring to the sourcemodel’s id.
- Modify the source model repository class to provide access to a constrainedtarget model repository.
- Call the constrained target model repository CRUD APIs in your controllermethods.
This section describes how to define a hasMany
relation at the model levelusing the @hasMany
decorator. The relation constrains the target repository bythe foreign key property on its associated model. The following example showshow to define a hasMany
relation on a source model Customer
.
/src/models/customer.model.ts
The decorated property name is used as the relation name and stored as part ofthe source model definition’s relation metadata. The property type metadata isalso preserved as an array of type Order
as part of the decoration.
A usage of the decorator with a custom foreign key name for the above example isas follows:
// import statements
class Customer extends Entity {
// constructor, properties, etc.
@hasMany(() => Order, {keyTo: 'customerId'})
orders?: Order[];
}
Add the source model’s id as the foreign key property (customerId
) in thetarget model.
/src/models/order.model.ts
The foreign key property (customerId
) in the target model can be added via acorresponding relation, too.
/src/models/order.model.ts
import {Entity, model, property, belongsTo} from '@loopback/repository';
import {Customer, CustomerWithRelations} from './customer.model';
@model()
export class Order extends Entity {
@property({
type: 'number',
id: true,
required: true,
@property({
type: 'string',
required: true,
})
name: string;
@belongsTo(() => Customer)
customerId: number;
constructor(data?: Partial<Order>) {
super(data);
}
}
export interface OrderRelations {
customer?: CustomerWithRelations;
}
export type OrderWithRelations = Order & OrderRelations;
The configuration and resolution of a hasMany
relation takes place at therepository level. Once hasMany
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:
- call the
createHasManyRepositoryFactoryFor
function in the constructor ofthe source repository class with the relation name (decorated relationproperty on the source model) and target repository instance and assign it theproperty mentioned above.The following code snippet shows how it would look like:
/src/repositories/customer.repository.ts
The following CRUD APIs are now available in the constrained target repositoryfactory orders
for instances of customerRepository
:
create
for creating a target model instance belonging to customer modelinstance(API Docs)find
finding target model instance(s) belonging to customer model instance()delete
for deleting target model instance(s) belonging to customer modelinstance(API Docs)- for patching target model instance(s) belonging to customer modelinstance()For updating (full replace of all properties on a
PUT
endpoint forinstance) a target model you have to directly use this model repository. In thiscase, the caller must provide both the foreignKey value and the primary key(id). Since the caller already has access to the primary key of the targetmodel, there is no need to go through the relation repository and the operationcan be performed directly onDefaultCrudRepository
for the target model(OrderRepository
in our example).
The same pattern used for ordinary repositories to expose their CRUD APIs viacontroller methods is employed for hasMany
repositories. Once the hasManyrelation has been defined and configured, controller methods can call theunderlying constrained repository CRUD APIs and expose them as routes oncedecorated withRoute decorators. Itwill require the value of the foreign key and, depending on the request method,a value for the target model instance as demonstrated below.
src/controllers/customer-orders.controller.ts
import {post, param, requestBody} from '@loopback/rest';
import {CustomerRepository} from '../repositories/';
import {Customer, Order} from '../models/';
import {repository} from '@loopback/repository';
export class CustomerOrdersController {
constructor(
@repository(CustomerRepository)
protected customerRepository: CustomerRepository,
) {}
@post('/customers/{id}/order')
async createOrder(
@param.path.number('id') customerId: typeof Customer.prototype.id,
@requestBody() orderData: Order,
): Promise<Order> {
return this.customerRepository.orders(customerId).create(orderData);
}
In LoopBack 3, the REST APIs for relations were exposed using static methodswith the name following the pattern {methodName}{relationName}
(e.g.Customer.
find__orders
). We recommend to create a new controller for eachrelation in LoopBack 4. First, it keeps controller classes smaller. Second, itcreates a logical separation of ordinary repositories and relationalrepositories and thus the controllers which use them. Therefore, as shown above,don’t add order-related methods to CustomerController
, but instead create anew CustomerOrdersController
class for them.
Note: