9

I am using Strapi CMS and struggling with fetching the nested/deep content’s data. E.g.: Let’s say, I have below content types created and relations are defined.

Person: Name, Age

Address: City, Country

Contact: Code, Number

Person has one Address

Address has many Contacts

Now the problem is, when I access ‘/persons’, I get only Name, Age and Address object. But address object does not have the contact information associated with the address.

Can somebody help me to get this resolved or point me towards any such article?

Ishwar Patil
  • 1,671
  • 3
  • 11
  • 19

6 Answers6

18

Firstly you'll need a custom controller function for this. In /api/person/controllers/Person.js you can export your custom find function. There you can define which fields you want to populate:

module.exports = {
  find: ctx => {
    return strapi.query('person').find(ctx.query, ['address', 'contact']);
  },
};

Another solution works for me as well:

module.exports = {
  find: ctx => {
    return strapi.query('person').find(ctx.query, [
       { path: 'address' },
       { path: 'contact' },
    ]);
  },
};

Edited example with one level deeper populate:

module.exports = {
  find: ctx => {
    return strapi.query('person').find(ctx.query, [
      {
        path: 'address',
        populate: {
          path: 'contacts',
        },
      },
    ]);
  },
};

For reference see the most recent beta docs:

https://strapi.io/documentation/3.0.0-beta.x/concepts/queries.html#api-reference

Roland Balogh
  • 231
  • 2
  • 4
  • Thanks for the tips, Roland. I will try to use this. However I am wondering, what if I want change the schema structure and add some more nested schema using Admin Panel. I will then have to change the controller again to resolve that problem. And what if I have multiple such higher level schema with nested schema. Is there a solution to handle this dynamically? – Ishwar Patil Nov 29 '19 at 04:55
  • Good question. I found two fields what you can try in the schema definition at the fields. One is `autoPopulate` (boolean) and the second is `private` (boolean). Unfortunately I didn't find information about these in the docs, but here is the issue and the comment where I first saw them: https://github.com/strapi/strapi/issues/2086#issuecomment-428176026 If you have multiple level of deepness eg.: your Address has many Contacts, you can extend the query to add more populate fields. I'll update the answer. – Roland Balogh Nov 29 '19 at 13:01
  • You've to add await before strapi.query(). You are calling inside async function. – Victor Ray Apr 09 '20 at 18:13
  • Not necessarily. Here we are returning with the query (and the result) immediately and just want to send it in the response. Strapi handles the rest. Maybe we could even remove the async here, I just always put it there if I want to work with the data before I send the response bc in that case you need the await. – Roland Balogh Apr 11 '20 at 12:48
  • @RolandBalogh I'm having some troubles with this too, I'm getting a 401 unauthorized error, I assume its because of a custom policy for data ownership I've created. Is there any way to include the policy on the query so that I don't get the error? If I remove the custom controller method I've no troubles getting the data (but I can't populate one level deeper) – Fjallbacka Apr 19 '20 at 13:09
  • @Fjallbacka can you write more info about your custom policy? Or how does it look like? What's been created in your policies folder? – Roland Balogh Apr 23 '20 at 05:36
4

I was able to get some nested data using the following:

api/booking/controllers/booking.js:

async find(ctx) {
    const entities = await strapi.services.booking.find(ctx.query, [
        'class',
        'class.capacity',
        'class.date',
        'class.category',
        'class.category.name',
        'class.type',
        'class.type.name',
        'class.startTime',
        'class.endTime',
      ]);
    }

    return entities.map((entity) =>
      sanitizeEntity(entity, { model: strapi.models.booking }),
    );
  },

where my booking has a relation to class and user. So, by default it just comes back with the class id's - but I'd like to be able to see fields from the class relation all in the same payload.

ie, instead of this:

user: "123eqwey12ybdsb233",
class: "743egwem67ybdsb311"

I'm trying to get:

user: {
  id: "123eqwey12ybdsb233",
  email: "foo@bar.com",
  ...
},
class: {
  id: "743egwem67ybdsb311",
  capacity: 10,
  type: {
    name: "Individual",
    description: "..."
    ...
  }
  ...
}

Now, the above works for non-relational fields.. but for fields that are a relation of a relation (ie. class.category and class.type), it doesn't seem to work as I would've expected.

In my database, the relation chain is like so: booking -> class -> category / type, where category and type each have a name and some other fields.

Mykhaylo
  • 39
  • 4
  • Hi, I'm currently having some problem with the nested User, it is returning the password even after I use `sanitizeEntity` is it expected? I'd prefer not to strip it manually if there's a utility function to do that since it is quite common. To fit to this use case my `User` is in `Booking` object to indicate whose Booking it is.. – Zennichimaro Jan 03 '21 at 00:04
  • 1
    @Zennichimaro what model are you using to sanitize? Here's how I accomplished similar case in my app: ```const booking = await strapi.query('booking').findOne(params, populate); // booking.author is just the ID string at this point, so we need to get the full relation object const author = strapi.query('user', 'users-permissions').findOne({ id: booking.author }, []); // update booking.author with the full user object and sanitize it booking.author = sanitizeEntity(user, { model: strapi.plugins['users-permissions'].models.user });``` – Mykhaylo Jan 05 '21 at 19:54
1

Only this worked for me

const data = await strapi
  .query("grand_collection")
  .model.find({ user: id })
  .populate({ path: "parent_collection", populate: { path: "child_collection" } });
Moiz Sohail
  • 558
  • 5
  • 22
0

None of the above answers worked for me unfortunately. I have a deeply nested relation that wasn't even show in the response(some people do get ID but I got nothing in the response).

Only thing that helped me was to build controller based off the suggestion in this issue here

Tom
  • 349
  • 1
  • 7
0

This one worked for me

let populate = ["parentCollection", "parentCollection.fieldToBePopulated"]
return await strapi.services.grandParentCollection.find({ condition }, populate)
Hekmat
  • 588
  • 6
  • 19
0

This seem to work for me, Strapi v.4

Fetch components and dynamic zone components, using https://www.npmjs.com/package/qs

export async function getStaticProps({ params }) {
  const query = qs.stringify(
    {
      populate: [
        "pageHeading",
        "seo",
        "socialMedia",
        "block.image",
        "block.addressCard.image",
      ],
    },
    {
      encodeValuesOnly: true,
    }
  );
  const { data } = await fetcher(
    `${process.env.NEXT_PUBLIC_STRAPI_API}/pages?publicationState=live&${query}&filters[slug]=${params.slug}`
  );

  return { props: { data } };
}

"pageHeading" - component

"block.addressCard.image" - addressCard nested component inside dynamic zone component

enter image description here enter image description here

More at: https://documentation-git-fixes-population-strapijs.vercel.app/developer-docs/latest/developer-resources/database-apis-reference/rest/populating-fields.html#component-dynamic-zones

atazmin
  • 4,757
  • 1
  • 32
  • 23