pratik jaiswal
pratik jaiswal

Reputation: 2065

Unhandled error in POST /order-products: 500 Error: Navigational properties are not allowed in model data

I am using loopback 4 relations. I have three models product, order, and orderProducts. The relationships are as follows

  1. order has many OrderProducts
  2. Product has many OrderProducts
  3. OrderProducts belongs to product.

But when I try post request on OrderProducts it is giving this error :

500 Error: Navigational properties are not allowed in model data (model "OrderProducts" property "product_id")

Here is my code. Thank you in advance

order.model.ts

import { Entity, model, property, hasMany} from '@loopback/repository';
import {OrderProducts} from './order-products.model';

@model({ settings: { strict: false } })
export class Orders extends Entity {
  @property({
    type: 'number',
    id: true,
    generated: true,
  })
  order_id?: number;

  @property({
    type: 'number',
    required: true,
  })
  total: number;

  @property({
    type: 'string',
  })
  get: string;

  @hasMany(() => OrderProducts, {keyTo: 'order_id'})
  orderProducts: OrderProducts[];
  // Define well-known properties here

  // Indexer property to allow additional data
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [prop: string]: any;

  constructor(data?: Partial<Orders>) {
    super(data);
  }
}

export interface OrdersRelations {
  // describe navigational properties here
}

export type OrdersWithRelations = Orders & OrdersRelations;

product.model.ts

import { Entity, model, property, hasMany } from '@loopback/repository';
import { OrderProducts } from './order-products.model';

@model({ settings: { strict: false } })
export class Product extends Entity {
  @property({
    type: 'number',
    id: true,
    generated: true,
  })
  product_id?: number;

  @property({
    type: 'string',
    required: true,
  })
  name: string;

  @property({
    type: 'string',
  })
  desc?: string;

  @hasMany(() => OrderProducts, { keyTo: 'product_id' })
  orderProducts: OrderProducts[];
  // Define well-known properties here

  // Indexer property to allow additional data
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [prop: string]: any;

  constructor(data?: Partial<Product>) {
    super(data);
  }
}

export interface ProductRelations {
  // describe navigational properties here
}

export type ProductWithRelations = Product & ProductRelations;

order-product.model.ts

import {Entity, model, property, belongsTo} from '@loopback/repository';
import {Product} from './product.model';

@model({settings: {strict: false}})
export class OrderProducts extends Entity {
  @property({
    type: 'number',
    id: true,
    generated: true,
  })
  o_id?: number;

  @property({
    type: 'number',
    required: true,
  })
  quantity: number;

  @property({
    type: 'number',
  })
  order_id?: number;

  @belongsTo(() => Product)
  product_id: number;
  // Define well-known properties here

  // Indexer property to allow additional data
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [prop: string]: any;

  constructor(data?: Partial<OrderProducts>) {
    super(data);
  }
}

export interface OrderProductsRelations {
  // describe navigational properties here
}

export type OrderProductsWithRelations = OrderProducts & OrderProductsRelations;

Upvotes: 1

Views: 1329

Answers (1)

AgnesLin
AgnesLin

Reputation: 26

I think the problem might be caused by the naming convention. LoopBack 4 uses camelCased variable name by default.

For any relation, there are three important attribute: the source key, foreign key, and the relation name.

Here's the link to the related files: Relation Metadata. For hasMany and hasOne relations, the source key defaults to the id property, which is Order.order_id and Product.product_id in you case. And the target key (foreign key) defaults 'TargetModelName' + 'Id'. Which is orderProducts.orderProductsId. And the default relation name is the one you decorated with @hasMany decorator, which is orderProducts in your example. You have keyFrom to deal with the customized names so the two hasMany relations are fine.

For belongsTo relation, the default source key is the id property of the target model, which is Product.product_id in your case. The source key is the one decorated by @belongsTo decorator, which is OrderProducts.product_id. And the default relation name is .. here is the tricky part, it is product_id in you case. Idealy, the source key is supposed to be OrderProducts.productId, and the default relation name will be product. In your case, the property has the same name as your relation name. This is caused by the way LB4 generates relation names. That's why it complains the navigational properties.

To fix it, you need to change two files, orderProducts.model.ts:

... // other properties

@belongsTo(() => Product, {name: 'product'}) // use a different name from the property name
  product_id: number;                        // let's use 'product' here
...

and OrderProductsRepository:


export class OrderProductsRepository extends DefaultCrudRepository<
  OrderProducts,
  typeof OrderProducts.prototype.o_id,
  OrderProductsRelations
> {
  public readonly product: BelongsToAccessor<  // use the relation name
    Product,
    typeof OrderProducts.prototype.o_id
  >;
  constructor(
    @inject('datasources.db') dataSource: DbDataSource,
    @repository.getter('ProductRepository')
    protected productRepositoryGetter: Getter<ProductRepository>,
  ) {
    super(OrderProducts, dataSource);

// make sure the name is correct
    this.product = this.createBelongsToAccessorFor(
      'product',
      productRepositoryGetter,
    );

// make sure the name is correct
    this.registerInclusionResolver( 
      'product',
      this.product.inclusionResolver,
    );
  }
}

Reference: BelongsTo Relation Metadata.

If you have any questions about LB4, you can also open an issue on our GitHub community.

Upvotes: 1

Related Questions