PUT request body becomes null

Hello,

I have the following service function:

/**
   * Retrieves a specific test suite by its ID.
   *
   * @route GET /testsuites/{suiteId}
   */
  async getTestSuite() {
    const suiteId = this.request.pathParams.suiteId;

    const whereClause = `suiteId = ${suiteId}`;
    const queryBuilder =
      Backendless.DataQueryBuilder.create().setWhereClause(whereClause);

    return await Backendless.Data.of("testSuites").find(queryBuilder);
  }

/**
   * Updates an existing test suite in the database.
   *
   * @param {Object} testSuite - The object containing updated test suite data.
   *
   * @route PUT /testsuites/{suiteId}
   */
  async updateTestSuite(testSuite) {
    return await Backendless.Data.of("testSuites").save(
      new TestSuite(
        testSuite.testSuiteName,
        await this.getTestSuite()[0].objectId
      )
    );
  }

However, when I test out PUT /testsuites/{suiteId} in the Backendless console, I get a 400 error even when a valid suite ID and test suite object is provided:

curl -X "PUT" "<my_api_url>/testsuites/1" \
	-H 'Content-Type: application/json' \
	-H 'Accept: application/json' \
	-d $'{
  "testSuiteName": "Test"
}'
{
    "code": 0,
    "message": "Cannot read properties of null (reading 'testSuiteName')",
    "errorData": {}
}

When I try to remove the {suiteId} path param from the PUT route, the API recognizes the request body. Is it not possible to have a path param and request body at the same time for PUT requests?

Hello @Vernon_Cenzon

Welcome to our community and thank you for trying out Backendless.

We will be happy to assist you. I need to ask you a few more questions so I can understand the problem better.
Is your application id 35914BA5-AEE4-E0BA-FF71-B4D4504FCB00?
I did not find any service (Backendless console - API Services - Services) in your app, or do you mean other services (service function)?
How can I reproduce your problem, please provide the steps to reproduce.

Regards,
Volodymyr

Hi @Volodymyr_Ialovyi ! Thanks for responding.

Is your application id 35914BA5-AEE4-E0BA-FF71-B4D4504FCB00?

Yes

I did not find any service (Backendless console - API Services - Services) in your app, or do you mean other services (service function)?

Sorry, yes I don’t have any API services deployed yet. I only test out in debug mode at the moment and that’s where I encountered the isssue.

How can I reproduce your problem, please provide the steps to reproduce.

  1. Configure a table named testSuites that has at least one column named suiteName (but this might be optional because I don’t think the issue lies with the call to Backendless.Data.of("testSuites").save(), but more on resolving testSuite.testSuiteName when creating a new TestSuite instance)
  2. Create a class for the TestSuite custom data type with at least a suiteName property.
  3. Create a service class with at least the 2 functions getTestSuite() and updateTestSuite() mentioned in my original post above.
  4. Run the API service in debug and make the PUT request with both a valid suiteId path param and testSuite object in the request payload

These are the steps I did that led me to encountering the issue but perhaps the issue would also be encountered even just with a PUT request to a URL with a path param and request object payload at the same time?

Update:

I tried modifying updateTestSuite() like this:

/**
   * Updates an existing test suite in the database.
   *
   * @param {String} testSuiteName - The new name of the test suite.
   * @param {String} [someOtherProperty] - Some other property.
   *
   * @route PUT /testsuites/{suiteId}
   */
  async updateTestSuite(testSuiteName, someOtherProperty) {
    // Retrieve test suite from the database based on {suiteId} path parameter to get the corresponding objectId
    const testSuite = await this.getTestSuite();


    return await Backendless.Data.of("testSuites").save(
      new TestSuite(testSuiteName, testSuite[0].objectId)
    );
  }

where instead of accepting a testSuite object containing a testSuiteName property as the argument, it now accepts 2 separate arguments testSuiteName and someOtherProperty (so that Backendless will make the request body schema an object). All other logic remains the same.

When I tested it, it worked as I intended.

I understand that there are better ways to implement this function (i.e. just have testSuiteName as the sole argument, or perhaps even directly include objectId as an argument as well to skip having to look for it in the DB). It just seems weird to me that something like this would work:

/**
* @param {String} arg1
* @param {String} arg2
* @route PUT /path/{param}
*/
myFunction(arg1, arg2) {
  return {"arg1": arg1, "arg2": arg2};
}

// curl -X "PUT" "path/1" \
//	-H 'Content-Type: application/json' \
//	-H 'Accept: application/json' \
//	-d $'{"arg1": "hello", "arg2": "world"}'

// > {arg1: "hello", arg2: "world"}

while something like this won’t:

/**
* @param {Object} arg1
* @route PUT /path/{param}
*/
myFunction(arg1) {
  return arg1;
}

// curl -X "PUT" "path/1" \
//	-H 'Content-Type: application/json' \
//	-H 'Accept: application/json' \
//	-d $'{"someProperty": "hello", "otherProperty": "world"}'

// > null

or maybe I’m just missing something?

Hello @Vernon_Cenzon!

Please note that within the body, keys must be used exactly as described in the @param annotation.
For instance, instead of using "testSuiteName": "Test", you should use "testSuite": "Test".
To illustrate with the last example you mentioned, the payload in the body should look like this:
{ "arg1": {"someProperty": "hello", "otherProperty": "world"} }

I hope this helps.

Regards,
Alexander