Loopback-next: loopback 4 how to create put method with two parameters

Created on 21 Jan 2020  路  25Comments  路  Source: strongloop/loopback-next

I have two models users and address. User has-many address and address belongs to users. Now I want to create put method on the following url

/users/{id}/address/{a_id}

The intention is to make a put request on one of many addresses belongs to a particular user.

This is what I have tried but can't get over it


@put('/users/{id}/addresses/{a_id}', {
    responses: {
      '200': {
        description: 'Users.Address PATCH success count',
        content: { 'application/json': { schema: CountSchema } },
      },
    },
  })
  async put(
    @param.path.number('id') id: number,
    @param.path.number('a_id') a_id: any,

    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(Address, { partial: true }),
        },
      },
    })
    address: Partial<Address>,
    @param.query.object('where', getWhereSchemaFor(Address)) where?: Where<Address>,
  ): Promise<Count> {
    return this.usersRepository.addresses(id).patch(address, where, a_id)
  }

I just come to know about userrepository(repository of source model) contains 4 methods: create, patch, delete, find. Now how can I make custom put a request with patch method of the repository?

Thank you in advance

question

All 25 comments

Hi @pratikjaiswal15 according to has many repository's patch method, I don't see obvious problem of your controller method.

This is what I have tried but can't get over it

Could you explain more about what does not work? Like are you getting error thrown from any code? Or 500 error? Or incorrect returned data?

Hmm, I think I find the issue, when patching the record:

return this.usersRepository.addresses(id).patch(address, where, a_id);

you should merge the a_id filter into where, instead of putting it as the 3rd parameter(options).

Try this: get your Address model's id name by

const adIdName = Address.definition.idName()

, then merge it into where:

where[adIdName] = a_id

Hello, @jannyHou Thank you for your reply. Actually I was getting internal server error 500. After your answer, I tried to merge a_id into where but can't get the right syntax. This is what I have tried

@param.query.object('where', getWhereSchemaFor(Address)) where?: Where[adIdName]=a_id,
Getting following erros

Parameter cannot have question mark and initializer.ts(1015)

Type 'any' cannot be used as an index type.ts(2538)

'adIdName' refers to a value, but is being used as a type here.ts(2749)

Parameter 'where' of public method from exported class has or is using private name 'adIdName'

Sorry to disturb you but can you please elaborate the right syntax to me. Thank you
All code :

import {} // all imports
const adIdName = Address.definition.id() // id is name of id parameter
export class UsersAddressController {
constructor(
    @repository(UsersRepository) protected usersRepository: UsersRepository,
  ) { }

@patch('/users/{id}/addresses/{a_id}', {
    responses: {
      '200': {
        description: 'Users.Address PATCH success count',
        content: { 'application/json': { schema: CountSchema } },
      },
    },
  })
  async put2(
    @param.path.number('id') id: number,
    @param.path.number('a_id') a_id: number,

    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(Address, { partial: true }),
        },
      },
    })

    address: Partial<Address>,
    @param.query.object('where', getWhereSchemaFor(Address)) where?: Where[adIdName]=a_id, // getting error on this line
  ): Promise<Count> {
    return this.usersRepository.addresses(id).patch(address, where);
  }

}

Hey @pratikjaiswal15

I think you are looking for the following approach that passes in the Address update in the request body along with the user's id and a where clause to select the addresses to update:

@patch('/users/{id}/addresses', {
    responses: {
      '200': {
        description: 'Users.Address PATCH success count',
        content: { 'application/json': { schema: CountSchema } },
      },
    },
  })
async put2(
    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(Address, { partial: true }),
        },
      },
    })
    address: Partial<Address>,
    @param.path.number('id') id: number,
    @param.query.object('where', getWhereSchemaFor(Address)) where?: Where<Address>,
  ): Promise<Count> {
    return this.usersRepository.addresses(id).patch(address, where);
  }

I think you can read the Where Filter documentation to learn how to pass in the where clause depending on how you're calling the api. If that is confusing please ask for more help and someone will try to guide you further.

Hey @dougal83 actually I want to update only single address of many addresses belongs to a particular user. Something like this
users/{id}/addresses/{adresses_id}.
As per instructions of @jannyHou I have to pass address_id to where filter to get respective address record and then pass where to patch method. But I can't get the right syntax.
Can you please help me in passing address_id to where filter.
Thank you very much

You pass the filter via the call to the API. With what I posted above as controller, using REST you can try:
/users/1/addresses?filter[where][id]=2
where 1 is the userId and 2 is the addressId.

It's something like that, read the aforementioned docs for more info. The reason for using a filter is to give flexibility but you can revert to a simple query param if you find it easier.

Okay, but I want to do it inside the controller method. This is what I have tried

const adIdName = Address.definition.id() // where id is address id
@param.query.object('where', getWhereSchemaFor(Address)) where[adIdName]= a_id, // inside method 

But getting error

Type 'number' must have a 'Symbol.iterator' method that returns an iterator

hope you can help me

Hello @jannyHou can you please help? please

all code

import {}
const adIdName = Address.definition.id() // after all imports

@patch('/users/{id}/addresses/{a_id}', {
    responses: {
      '200': {
        description: 'Users.Address PATCH success count',
        content: { 'application/json': { schema: CountSchema } },
      },
    },
  })
  async put2(
    @param.path.number('id') id: number,
    @param.path.number('a_id') a_id: number,

    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(Address, { partial: true }),
        },
      },
    })

    address: Partial<Address>,
    @param.query.object('where', getWhereSchemaFor(Address)) where[adIdName]= a_id,
  ): Promise<Count> {
    return this.usersRepository.addresses(id).patch(address);
  }

Hey @pratikjaiswal15,

It appears that you wish to persist with fixed parameters.

Remove:

@param.query.object('where', getWhereSchemaFor(Address)) where[adIdName]= a_id,

Use (assuming adIdName is the field to check):

return this.usersRepository.addresses(id).patch(address, {adIdName: a_id});

I highly recommend that you familiarise yourself with the Where Filter documentation. Please don't be put off by it not being labelled as lb4, the lb3 docs still apply here.

@dougal83 Okay I got your point. But the problem is @patch('/users/{id}/addresses will update all the addresses belongs to a particular user. Now I have to use where to filter single address and pass to patch method

@dougal83 Okay I got your point. But the problem is @patch('/users/{id}/addresses will update all the addresses belongs to a particular user. Now I have to use where to filter single address and pass to patch method

The path @patch('/users/{id}/addresses accompanied with a where filter param will only update records that satisfy the filter. Don't worry about using the where filter for now, you can look into that at a later date.

My previous post was in reference to the your code in the post immediately above it. I'll be explicit, try the following:

@patch('/users/{id}/addresses/{a_id}', {
    responses: {
      '200': {
        description: 'Users.Address PATCH success count',
        content: { 'application/json': { schema: CountSchema } },
      },
    },
  })
  async put2(
    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(Address, { partial: true }),
        },
      },
    })
    address: Partial<Address>,
    @param.path.number('id') id: User.prototype.id,
    @param.path.number('a_id') a_id: Address.prototype.id,
  ): Promise<Count> {
    return this.usersRepository.addresses(id).patch(address, {adIdName: a_id});
  }

If you are using an editor like VSCode then your should see code hints as you hover above elements in your code. For example .patch hints at how you can apply the filter in the example above.

EDIT: Address.prototype.id assumes that Address model Id is named id. Param also assumes that the id is a number.

Okay. Thank you very much but it is giving error. Stating
Address.definition.id() is not function
Id is name of address id

Okay. Thank you very much but it is giving error. Stating
Address.definition.id() is not function
Id is name of address id

Is that your code from above?:

const adIdName = Address.definition.id() // after all imports

Not sure why you've got that up there... delete it?

Actually It was referred by @jannyHou to get id of address. If I remove it, what is adIdName in your code which is passed to patch method? Where to declare it?

Actually It was referred by @jannyHou to get id of address. If I remove it, what is adIdName in your code which is passed to patch method? Where to declare it?

In place of adIdName on my example put the id property name for your address model (usually id). @jannyHou appears to be showing you how to find that programmatically. Since you are specifically passing in the two parameters you can just hardcode the where clause:

.patch(address, {id: a_id});

Assuming that the Address Id property is named id. Change according to your own model definition.

Sorry for the late reply. Thank you very much. It is working fine

You're most welcome, happy to help. Closing issue.

Hello, @dougal83 So sorry to disturb you again. But I can't create get request with two parameters.
Here is my code

@get('/users/{id}/carts/{cart_id}', {
    responses: {
      '200': {
        description: 'Array of Cart\'s belonging to Users',
        content: {
          'application/json': {
            schema: { type: 'array', items: getModelSchemaRef(Cart) },
          },
        },
      },
    },
  })
  async find_id(
    @param.path.number('id') id: number,
    @param.path.number('cart_id') cart_id: number,

  //  @param.query.object('filter') filter?: Filter<Cart>,
  ): Promise<Cart[]> {
    return this.usersRepository.carts(id).find({cart_id: cart_id}); //  error
  }

Error I am getting following error

Argument of type '{ cart_id: number; }' is not assignable to parameter of type 'Filter<Cart>'.
  Object literal may only specify known properties, and 'cart_id' does not exist in type 'Filter<Cart>'

So embarrassed to disturb you. Thank you in advance

No worries.

.find({id: cart_id}) would be my wild stab in the dark.

No not working. Getting same error. In this example id field of the modal cart is cart_id

Whoops, I meant to add where part too, try:
.find({where: {cart_id: cart_id}})

It makes more sense to me to name the Id of the cart model as id, each to their own. Read docs and experiment to see if you can get it working as you expect. The example shopping app is worth taking a look at too if you wish to see a more complete example.

See LB3 docs on querying data, it's similar(or even the same) to LB4. It should help you understand how to use the filters.

Thanks a lot.

Small question though. Can you please tell me how to get the count on an extension.

For example users/9/carts/count will give the count of records on users/9/carts.
I googled a lot but didn't get any solution. It is not been documented very well.

Thank you

You could do:

  @get('/users/{id}/carts/count', {
    responses: {
      '200': {
        description: 'Users Cart model count',
        content: {'application/json': {schema: CountSchema}},
      },
    },
  })
  async count(
    @param.path.number('id') id: number,
  ): Promise<Count> {
    const foundCarts = await this.userRepository.carts(id).find();
    return {count: foundCarts.length};
  }

More typically though you would use the feature found in a default CRUD controller for the Cart repository:

  @get('/carts/count', {
    responses: {
      '200': {
        description: 'Cart model count',
        content: {'application/json': {schema: CountSchema}},
      },
    },
  })
  async count(
    @param.query.object('where', getWhereSchemaFor(User)) where?: Where<Cart>,
  ): Promise<Count> {
    return this.cartRepository.count(where);
  }

You could supply a where clause to specify the userId, e.g. {where: { userId: <insert user id here>} } assuming the id for user model relation is named userId.

Thank you very much

Was this page helpful?
0 / 5 - 0 ratings