Inconsistency between responses gotten from the REST API and the Entity service API

System Information
  • Strapi Version:
  • Operating System:
  • Database:
  • Node Version:
  • NPM Version:
  • Yarn Version:

When getting records using the entity service API by doing something like

const data = await strapi.entityService.findMany('api::book.book', {
  where: {
    id: 1
  },
});

each entry is displayed as a hash containing the “id” field alongside the attributes at the same level of the hash, like here:

{
    "id": 1,
    "name": "Book name",
    "website": "example.com",
    "facebook_page_username": "examplepage",
    "slug": "book-page",
    "description": "book description",
    "createdAt": "2024-01-03T17:01:26.137Z",
    "updatedAt": "2024-01-03T20:51:05.045Z",
    "publishedAt": "2024-01-03T17:01:29.730Z"
}

However, when querying this same record using the REST api by calling an endpoint like http://localhost:1337/api/books/1, we get for each enty a hash with 2 keys, “id” and “attributes” following this pattern:

{
    "id": 1,
    "attributes": {
        "name": "Book name",
        "website": "example.com",
        "facebook_page_username": "examplepage",
        "slug": "book-page",
        "description": "book description",
        "createdAt": "2024-01-03T17:01:26.137Z",
        "updatedAt": "2024-01-03T20:51:05.045Z",
        "publishedAt": "2024-01-03T17:01:29.730Z"
    }
}

This also applies to each element populated. For instance, if I populate an entry like “images”, I would get the images as an array of hashes where each hash has “attributes” and “id” keys in the second case but where attributes and the id would be all at the same level for the first case.

I am initially using the REST API but I had to add some custom routes where I am using the entity service API to retrieve data, my code is expecting hashes of “id” and “attributes” keys.

Ideally, I would like to have my api calls to the default rest routes as well as my calls to my added routes using custom controller return entries in the same pattern. Is this possible?

Update: I could solve this issue using the strapi-plugin-transformer plugin I was referred to in this thread:

Yes that was what I was going to recommend. Another approach you can use, is to write a transformer function to flatten the response.

export function flattenAttributes(data: any): any {
  // Base case for recursion
  if (!data) return null;

  // Handling array data
  if (Array.isArray(data)) {
    return data.map(flattenAttributes);
  }

  let flattened: { [key: string]: any } = {};

  // Handling attributes
  if (data.attributes) {
    for (let key in data.attributes) {
      if (
        typeof data.attributes[key] === "object" &&
        data.attributes[key] !== null &&
        "data" in data.attributes[key]
      ) {
        flattened[key] = flattenAttributes(data.attributes[key].data);
      } else {
        flattened[key] = data.attributes[key];
      }
    }
  }

  // Copying non-attributes and non-data properties
  for (let key in data) {
    if (key !== "attributes" && key !== "data") {
      flattened[key] = data[key];
    }
  }

  // Handling nested data
  if (data.data) {
    flattened = { ...flattened, ...flattenAttributes(data.data) };
  }

  return flattened;
}
1 Like

Yes, the transform plugin is the way to go.

But I’ve recently learned that it’s all a joke.

You see, core controllers invoke the transformResponse function which iterates over the service response and produces this highly impractical data/meta/attributes structure. This takes time.

Then, the middleware offered by the transform plugin iterates again over that transformed response and flattens it. Which takes time again.

A performant way would be to disable that transformResponse function alltogether, or at least the part that wraps all non-id field within attributes.

Maybe patch-package could be used for that.

3 Likes

I agree, it would be nice to be able to disable that behavior. Per entity might be nice for backward compatibility.