理解 REST API 中的 populate 参数

页面摘要: REST API 查询中的 populate 参数会在默认属性之外,将额外的字段、关联、组件和动态区域包含进响应中。使用 populate=* 可联表加载(populate)所有 1 层深度的关联,或者使用嵌套数组和 fragment(片段)语法显式指定字段,以进行更深层次或有选择性的联表加载。

说明:示例响应可能与你的实际体验有所不同

本页内容可能尚未完全与 Strapi 5 同步:

  • 所有概念性信息和解释都是正确的,并且是最新的。
  • 但是,在示例中,响应内容可能略有不同。

这些示例将在 Strapi 5.0.0(稳定版)发布之后,并且 FoodAdvisor 示例应用升级到 Strapi 5 之后,才会完全更新。

不过,响应示例略有不同,不应影响你掌握本页所教授的核心概念。

当使用 Strapi 的 REST API 查询内容类型时,默认情况下,响应只包含顶层字段,不包含任何关联、媒体字段、组件或动态区域。

在 Strapi REST API 的语境下,联表加载(populating)是指:通过在默认返回的字段之外返回更多字段,将额外的内容包含进你的响应中。你可以使用 populate 参数 来实现这一点。

INFO

在本指南中,示例均使用随 FoodAdvisor 示例应用一起提供的服务端中真实查询到的数据构建。若要自行测试示例,请搭建 FoodAdvisor,在 /api/ 文件夹中启动服务端,并在发送查询之前,确保为所查询的内容类型授予了适当的 find 权限。

本指南将详细解释以下使用场景:

INFO

联表加载多层深度通常被称为“深度联表加载(deep populate)”。

进阶用例:联表加载 creator 字段

除了在查询中以各种方式使用 populate 参数之外,你还可以构建一个自定义控制器作为变通方法,来联表加载 creator 字段(例如 createdBy 和 updatedBy)。这在专门的 How to populate creator fields(如何联表加载 creator 字段) 指南中有详细说明。

Populate all relations and fields, 1 level deep

你可以仅用一次查询就返回所有关联、媒体字段、组件和动态区域。对于关联,这仅在 1 层深度内有效,以防止性能问题和较长的响应时间。

要联表加载所有内容(深度 1 层),请在查询中添加 populate=* 参数。

下面的示意图比较了 FoodAdvisor 示例应用在是否联表加载所有内容(深度 1 层)时的数据:

Diagram with populate use cases with FoodAdvisor data

我们来对比并解释使用和不使用此查询参数时会发生什么:

Example: Without populate

如果不使用 populate 参数,向 /api/articles 发送的 GET 请求只返回默认属性,不会返回任何媒体字段、关联、组件或动态区域。

以下示例是来自 articles 内容类型的全部 4 条条目的完整响应。

请注意,响应仅包含 title、slug、createdAt、updatedAt、publishedAt 和 locale 字段,以及由 CKEditor 插件处理的文章内容字段(ckeditor_content,为简洁起见已截断):

仅返回默认属性,不包含任何媒体字段、关联、组件或动态区域。

cURL

curl 'http://localhost:1337/api/articles' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "documentId": "t3q2i3v1z2j7o8p6d0o4xxg",
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "// truncated content"
    },
    {
      "id": 2,
      "documentId": "k2r5l0i9g3u2j3b4p7f0sed",
      "title": "What are chinese hamburgers and why aren't you eating them?",
      "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
      "createdAt": "2021-11-11T13:33:19.948Z",
      "updatedAt": "2023-06-01T14:32:50.984Z",
      "publishedAt": "2022-09-22T12:36:48.312Z",
      "locale": "en",
      "ckeditor_content": "// truncated content"
    },
    {
      "id": 3,
      "documentId": "k6m6l9q0n6v9z2m3i0z5jah",
      "title": "7 Places worth visiting for the food alone",
      "slug": "7-places-worth-visiting-for-the-food-alone",
      "createdAt": "2021-11-12T13:33:19.948Z",
      "updatedAt": "2023-06-02T11:30:00.075Z",
      "publishedAt": "2023-06-02T11:30:00.075Z",
      "locale": "en",
      "ckeditor_content": "// truncated content"
    },
    {
      "id": 4,
      "documentId": "d5m4b6z6g5d9e3v1k9n5gbn",
      "title": "If you don't finish your plate in these countries, you might offend someone",
      "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
      "createdAt": "2021-11-15T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:59:35.148Z",
      "publishedAt": "2022-09-22T12:35:53.899Z",
      "locale": "en",
      "ckeditor_content": "// truncated content"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}

Example: With populate=*

使用 populate=* 参数后,向 /api/articles 发送的 GET 请求还会返回所有媒体字段、一级关联、组件和动态区域。

以下示例是来自 articles 内容类型的全部 4 条条目中第 1 条的完整响应(id 为 2、3、4 的文章数据已截断以简洁起见)。

向下滚动可以看到,响应体积比不使用 populate 时要大得多。响应现在包含了额外的字段(见高亮行),例如:

  • image 媒体字段(它存储了有关文章封面图的所有信息,包括其所有不同的格式),
  • blocks 动态区域和 seo 组件的 1 级字段,
  • category 关联及其字段,
  • 甚至还有一些关于翻译成其它语言的文章的信息,正如 localizations 对象所示。
TIP

要联表加载深层嵌套的组件,请参阅 populate components(联表加载组件) 一节。

返回所有媒体字段、一级关联、组件和动态区域。

cURL

curl 'http://localhost:1337/api/articles?populate=*' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles?populate=*',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "// truncated content",
      "image": {
        "data": {
            "id": 12,
            "documentId": "o5d4b0l4p8l4o4k5n1l3rxa",
            "name": "Basque dish",
            "alternativeText": "Basque dish",
            "caption": "Basque dish",
            "width": 758,
            "height": 506,
            "formats": {
              "thumbnail": {
                "name": "thumbnail_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg",
                "hash": "thumbnail_basque_cuisine_17fa4567e0_f033424240",
                "ext": ".jpeg",
                "mime": "image/jpeg",
                "width": 234,
                "height": 156,
                "size": 11.31,
                "path": null,
                "url": "/uploads/thumbnail_basque_cuisine_17fa4567e0_f033424240.jpeg"
              },
              "medium": {
                "name": "medium_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg",
                "hash": "medium_basque_cuisine_17fa4567e0_f033424240",
                "ext": ".jpeg",
                "mime": "image/jpeg",
                "width": 750,
                "height": 501,
                "size": 82.09,
                "path": null,
                "url": "/uploads/medium_basque_cuisine_17fa4567e0_f033424240.jpeg"
              },
              "small": {
                "name": "small_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg",
                "hash": "small_basque_cuisine_17fa4567e0_f033424240",
                "ext": ".jpeg",
                "mime": "image/jpeg",
                "width": 500,
                "height": 334,
                "size": 41.03,
                "path": null,
                "url": "/uploads/small_basque_cuisine_17fa4567e0_f033424240.jpeg"
              }
            },
            "hash": "basque_cuisine_17fa4567e0_f033424240",
            "ext": ".jpeg",
            "mime": "image/jpeg",
            "size": 58.209999999999994,
            "url": "/uploads/basque_cuisine_17fa4567e0_f033424240.jpeg",
            "previewUrl": null,
            "provider": "local",
            "provider_metadata": null,
            "createdAt": "2021-11-23T14:05:33.460Z",
            "updatedAt": "2021-11-23T14:05:46.084Z"
            }
          }
        },
        "blocks": [
          {
            "id": 2,
            "__component": "blocks.related-articles"
          },
          {
            "id": 2,
            "documentId": "w8r5k8o8v0t9l9e0d7y6vco",
            "__component": "blocks.cta-command-line",
            "theme": "primary",
            "title": "Want to give a try to a Strapi starter?",
            "text": "❤️",
            "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git"
          }
        ],
        "seo": {
          "id": 1,
          "documentId": "h7c8d0u3i3q5v1j3j3r4cxf",
          "metaTitle": "Articles - FoodAdvisor",
          "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor",
          "keywords": "food",
          "metaRobots": null,
          "structuredData": null,
          "metaViewport": null,
          "canonicalURL": null
        },
        "category": {
          "data": {
            "id": 4,
            "documentId": "t1t3d9k6n1k5a6r8l7f8rox",
            "name": "European",
            "slug": "european",
            "createdAt": "2021-11-09T13:33:20.123Z",
            "updatedAt": "2021-11-09T13:33:20.123Z"
          }
        },
        "localizations": {
          "data": [
            {
              "id": 10,
              "documentId": "h7c8d0u3i3q5v1j3j3r4cxf",
              "title": "Voici pourquoi il faut essayer la cuisine basque, selon un chef basque",
              "slug": "voici-pourquoi-il-faut-essayer-la-cuisine-basque-selon-un-chef-basque",
              "createdAt": "2021-11-18T13:33:19.948Z",
              "updatedAt": "2023-06-02T10:57:19.606Z",
              "publishedAt": "2022-09-22T13:00:00.069Z",
              "locale": "fr-FR",
              "ckeditor_content": "// truncated content"
            }
          ]
        }
      }
    },
    {
      "id": 2,
      "// truncated content": true
    },
    {
      "id": 3,
      "// truncated content": true
    },
    {
      "id": 4,
      "// truncated content": true
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}

Populate specific relations and fields

你也可以联表加载指定的关联和字段,方法是显式定义要联表加载的内容。这要求你知晓要联表加载的字段和关联的名称。

以这种方式联表加载的关联和字段可以是 1 层深度,也可以是多层深度。下面的示意图比较了向 FoodAdvisor 示例应用发送查询时,联表加载 1 层深度 与 联表加载 2 层深度 所返回的数据:

Diagram with populate use cases with FoodAdvisor data

🤓 针对相似结果的不同联表加载策略 根据你内容结构的不同,你可能会通过不同的查询以不同的方式获取到相似的数据。例如,FoodAdvisor 示例应用包含 article、category 和 restaurant 这几个相互以不同方式关联的内容类型。这意味着,如果你想在单个 GET 请求中获取这 3 个内容类型的数据,你有 2 种选择:

  • 查询 articles 并联表加载 categories,再加上 categories 与 restaurants 之间的嵌套关联(联表加载 2 层深度)
  • 查询 categories 并同时联表加载 articles 和 restaurants,因为 categories 与另外 2 个内容类型都有 1 级关联(联表加载 1 层深度)

这 2 种不同的策略如下图所示:

Diagram with populate use cases with FoodAdvisor data

populate 作为对象 vs. populate 作为数组:使用交互式查询构建器

高级查询参数的语法手动构建起来可能相当复杂。我们建议使用我们的 interactive query builder(交互式查询构建器) 工具来生成 URL。

使用该工具,你将以熟悉的(JavaScript)格式编写干净、可读的请求,这应该能帮助你理解不同查询之间以及不同联表加载方式之间的差异。例如,联表加载 2 层深度意味着将 populate 用作对象,而联表加载多个关联(深度 1 层)则意味着将 populate 用作数组:

将 populate 用作对象 (以联表加载 1 个关联的多层深度):

{
  populate: {
    category: {
      populate: ['restaurants'],
    },
  },
}

将 populate 用作数组 (以联表加载多个关联,深度 1 层)

{
  populate: [
    'articles',
    'restaurants'
  ],
}

Populate 1 level deep for specific relations

你可以通过将 populate 参数用作数组,来联表加载指定的关联(深度 1 层)。

由于 REST API 使用 LHS bracket notation(左侧方括号表示法)(即使用方括号 []),联表加载 1 层深度的参数语法如下所示:

要联表加载多少个关联语法示例
仅 1 个关联populate[0]=a-relation-name
多个关联populate[0]=relation-name&populate[1]=another-relation-name&populate[2]=yet-another-relation-name

我们来对比并解释,向 FoodAdvisor 示例应用发送查询时,联表加载 1 层深度的关联与不联表加载关联会发生什么:

Example: Without populate

如果不使用 populate 参数,向 /api/articles 发送的 GET 请求只返回默认属性。

以下示例是来自 articles 内容类型的全部 4 条条目的完整响应。

请注意,响应不包含任何媒体字段、关联、组件或动态区域:

返回所有文章的默认属性。

cURL

curl 'http://localhost:1337/api/articles' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "documentId": "x2m0d7d9o4m2z3u2r2l9yes",
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "…"
    },
    {
      "id": 2,
      "documentId": "k6m6l9q0n6v9z2m3i0z5jah",
      "title": "What are chinese hamburgers and why aren't you eating them?",
      "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
      "createdAt": "2021-11-11T13:33:19.948Z",
      "updatedAt": "2023-06-01T14:32:50.984Z",
      "publishedAt": "2022-09-22T12:36:48.312Z",
      "locale": "en",
      "ckeditor_content": "…"
    },
    {
      "id": 3,
      "documentId": "o5d4b0l4p8l4o4k5n1l3rxa",
      "title": "7 Places worth visiting for the food alone",
      "slug": "7-places-worth-visiting-for-the-food-alone",
      "createdAt": "2021-11-12T13:33:19.948Z",
      "updatedAt": "2023-06-02T11:30:00.075Z",
      "publishedAt": "2023-06-02T11:30:00.075Z",
      "locale": "en",
      "ckeditor_content": "…"
    },
    {
      "id": 4,
      "documentId": "t3q2i3v1z2j7o8p6d0o4xxg",
      "title": "If you don't finish your plate in these countries, you might offend someone",
      "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
      "createdAt": "2021-11-15T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:59:35.148Z",
      "publishedAt": "2022-09-22T12:35:53.899Z",
      "locale": "en",
      "ckeditor_content": "…"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}

Example: With populate[0]=category

在请求中添加 populate[0]=category 后,我们显式要求包含 category 的一些信息,它是一个将 articles 和 categories 内容类型关联起来的关联字段。

以下示例是来自 articles 内容类型的全部 4 条条目的完整响应。

请注意,响应现在为每篇文章包含了带有 category 字段的额外数据(见高亮行):

返回已联表加载相关 category 数据的文章。

cURL

curl 'http://localhost:1337/api/articles?populate[0]=category' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles?populate[0]=category',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "documentId": "w8r5k8o8v0t9l9e0d7y6vco",
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 4,
          "documentId": "u6x8u7o7j5q1l5y3t8j9yxi",
          "name": "European",
          "slug": "european",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z"
        }
      }
    },
    {
      "id": 2,
      "documentId": "k6m6l9q0n6v9z2m3i0z5jah",
      "title": "What are chinese hamburgers and why aren't you eating them?",
      "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
      "createdAt": "2021-11-11T13:33:19.948Z",
      "updatedAt": "2023-06-01T14:32:50.984Z",
      "publishedAt": "2022-09-22T12:36:48.312Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 13,
          "documentId": "x2m0d7d9o4m2z3u2r2l9yes",
          "name": "Chinese",
          "slug": "chinese",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z"
        }
      }
    },
    {
      "id": 3,
      "title": "7 Places worth visiting for the food alone",
      "slug": "7-places-worth-visiting-for-the-food-alone",
      "createdAt": "2021-11-12T13:33:19.948Z",
      "updatedAt": "2023-06-02T11:30:00.075Z",
      "publishedAt": "2023-06-02T11:30:00.075Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 3,
          "documentId": "h7c8d0u3i3q5v1j3j3r4cxf",
          "name": "International",
          "slug": "international",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z"
        }
      }
    },
    {
      "id": 4,
      "documentId": "t1t3d9k6n1k5a6r8l7f8rox",
      "title": "If you don't finish your plate in these countries, you might offend someone",
      "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
      "createdAt": "2021-11-15T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:59:35.148Z",
      "publishedAt": "2022-09-22T12:35:53.899Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 3,
          "documentId": "u6x8u7o7j5q1l5y3t8j9yxi",
          "name": "International",
          "slug": "international",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z"
        }
      }
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}

Populate several levels deep for specific relations

你也可以联表加载指定关联的多层深度。例如,当你联表加载一个本身又联表加载了另一个关联的关联时,你就是在联表加载 2 层深度。本指南涵盖的示例正是联表加载 2 层深度。

WARNING

可以联表加载的层级数没有限制。但是,联表加载的层级越深,请求执行所花费的时间就越长。

由于 REST API 使用 LHS bracket notation(左侧方括号表示法)(即使用方括号 []),例如,如果你想联表加载嵌套在另一个关联内部的关联,参数语法如下所示:

populate[first-level-relation-to-populate][populate][0]=second-level-relation-to-populate

TIP

高级查询参数的语法手动构建起来可能相当复杂。我们建议使用我们的 interactive query builder(交互式查询构建器) 工具来生成 URL。例如,下面示例中使用的 /api/articles?populate[category][populate][0]=restaurants URL,就是通过使用我们的工具转换以下对象生成的:

{
  populate: {
    category: {
      populate: ['restaurants'],
    },
  },
}

FoodAdvisor 示例应用包含内容类型之间各种层级的关联。例如:

  • 一个 article 内容类型包含一个与 category 内容类型的关联,
  • 但一个 category 也可以被分配给任意 restaurant 内容类型。

通过对 /api/articles 发送一次 GET 请求并配合使用适当的 populate 参数,你可以同时返回关于 articles、restaurants 和 categories 的信息。

我们来对比并解释,向 FoodAdvisor 发送查询时,使用 populate[0]=category(1 层深度)与 populate[category][populate][0]=restaurants(2 层深度)所返回的响应:

Example: With 1-level deep population

当我们仅联表加载 1 层深度,即请求与文章相关联的 categories 时,我们可以得到如下示例响应(高亮行展示了 category 关联字段):

联表加载 category 关联,深度 1 层。

cURL

curl 'http://localhost:1337/api/articles?populate[0]=category' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles?populate[0]=category',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "documentId": "9ih6hy1bnma3q3066kdwt3",
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 4,
          "name": "European",
          "slug": "european",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z"
        }
      }
    },
    {
      "id": 2,
      "documentId": "sen6qfgxcac13pwchf8xbu",
      "title": "What are chinese hamburgers and why aren't you eating them?",
      "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
      "createdAt": "2021-11-11T13:33:19.948Z",
      "updatedAt": "2023-06-01T14:32:50.984Z",
      "publishedAt": "2022-09-22T12:36:48.312Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 13,
          "documentId": "r3rhzcxd7gjx07vkq3pia5",
          "name": "Chinese",
          "slug": "chinese",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z"
        }
      }
    },
    {
      "id": 3,
      "documentId": "s9uu7rkukhfcsmj2e60b67",
      "title": "7 Places worth visiting for the food alone",
      "slug": "7-places-worth-visiting-for-the-food-alone",
      "createdAt": "2021-11-12T13:33:19.948Z",
      "updatedAt": "2023-06-02T11:30:00.075Z",
      "publishedAt": "2023-06-02T11:30:00.075Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 3,
          "documentId": "4sevz15w6bdol6y4t8kblk",
          "name": "International",
          "slug": "international",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z"
        }
      }
    },
    {
      "id": 4,
      "documentId": "iy5ifm3xj8q0t8vlq6l23h",
      "title": "If you don't finish your plate in these countries, you might offend someone",
      "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
      "createdAt": "2021-11-15T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:59:35.148Z",
      "publishedAt": "2022-09-22T12:35:53.899Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 3,
          "documentId": "0eor603u8qej933maphdv3",
          "name": "International",
          "slug": "international",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z"
        }
      }
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}

Example: With 2-level deep population

当我们联表加载 2 层深度,请求与文章相关联的 categories,以及这些 categories 相关联的 restaurants 时,我们可以得到如下示例响应。

请注意,我们现在在 category 关联内部得到了包含在响应中的 restaurants 关联字段(见高亮行):

联表加载 category 关联以及嵌套的 restaurants 关联,深度 2 层。

cURL

curl 'http://localhost:1337/api/articles?populate[category][populate][0]=restaurants' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles?populate[category][populate][0]=restaurants',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "documentId": "iy5ifm3xj8q0t8vlq6l23h",
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "…",
      "category": {
        "data": {
          "id": 4,
          "name": "European",
          "slug": "european",
          "createdAt": "2021-11-09T13:33:20.123Z",
          "updatedAt": "2021-11-09T13:33:20.123Z",
          "restaurants": {
            "data": [
              {
                "id": 1,
                "documentId": "ozlqrdxpnjb7wtvf6lp74v",
                "name": "Mint Lounge",
                "slug": "mint-lounge",
                "price": "p3",
                "createdAt": "2021-11-09T14:07:47.125Z",
                "updatedAt": "2021-11-23T16:41:30.504Z",
                "publishedAt": "2021-11-23T16:41:30.501Z",
                "locale": "en"
              },
              {
                "id": 9,
                "// truncated content": true
              },
              {
                "id": 10,
                "// truncated content": true
              },
              {
                "id": 12,
                "// truncated content": true
              },
              {
                "id": 21,
                "// truncated content": true
              },
              {
                "id": 26,
                "// truncated content": true
              }
            ]
          }
        }
      }
    },
    {
      "id": 2,
      "// truncated content": true
    },
    {
      "id": 3,
      "// truncated content": true
    },
    {
      "id": 4,
      "// truncated content": true
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}

Populate components

组件和动态区域默认不包含在响应中,你需要显式联表加载每个动态区域、组件,以及它们嵌套的组件。

由于 REST API 使用 LHS bracket notation(左侧方括号表示法)(即使用方括号 []),你需要在一个 populate 数组中传入所有元素。也可以传入嵌套字段,参数语法可能如下所示:

populate[0]=a-first-field&populate[1]=a-second-field&populate[2]=a-third-field&populate[3]=a-third-field.a-nested-field&populate[4]=a-third-field.a-nested-component.a-nested-field-within-the-component

TIP

高级查询参数的语法手动构建起来可能相当复杂。我们建议使用我们的 interactive query builder(交互式查询构建器) 工具来生成 URL。例如,下面示例中使用的 /api/articles?populate[0]=seo&populate[1]=seo.metaSocial&populate[2]=seo.metaSocial.image URL,就是通过使用我们的工具转换以下对象生成的:

{
  populate: [
    'seoData',
    'seoData.sharedImage',
    'seoData.sharedImage.media',
  ],
},

FoodAdvisor 示例应用包含各种组件,甚至包含嵌套在其它组件内部的组件。例如:

  • 一个 article 内容类型包含一个 seo 组件 > **提示:**此处原本是一个交互式组件(ScreenshotNumberReference),在静态 Markdown 中无法渲染。可参考原始在线文档查看对应内容。,
  • seo 组件包含一个可重复的嵌套 metaSocial 组件 > **提示:**此处原本是一个交互式组件(ScreenshotNumberReference),在静态 Markdown 中无法渲染。可参考原始在线文档查看对应内容。,
  • 而 metaSocial 组件本身又有若干字段,包括一个 image 媒体字段 > **提示:**此处原本是一个交互式组件(ScreenshotNumberReference),在静态 Markdown 中无法渲染。可参考原始在线文档查看对应内容。。

FoodAdvisor's SEO component structure in the Content-Type Builder

默认情况下,在向 /api/articles 发送的 GET 请求的响应中,不会包含这些字段或组件中的任何一个。但通过适当的 populate 参数,你可以在一次请求中返回它们全部。

我们来对比并解释,使用 populate[0]=seo(1 级组件)与 populate[0]=seo&populate[1]=seo.metaSocial(嵌套在 1 级组件内部的 2 级组件)所返回的响应:

Example: Only 1st level component

当我们只联表加载 seo 组件时,我们仅深入 1 层,可以得到如下示例响应。高亮行展示了 seo 组件。

请注意,没有提及嵌套在 seo 组件内部的 metaSocial 组件:

仅联表加载 seo 组件,深度 1 层。

cURL

curl 'http://localhost:1337/api/articles?populate[0]=seo' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles?populate[0]=seo',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "documentId": "md60m5cy3dula5g87x1uar",
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "…",
      "seo": {
        "id": 1,
        "documentId": "kqcwhq6hes25kt9ebj8x7j",
        "metaTitle": "Articles - FoodAdvisor",
        "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor",
        "keywords": "food",
        "metaRobots": null,
        "structuredData": null,
        "metaViewport": null,
        "canonicalURL": null
      }
    },
    {
      "id": 2,
      "// truncated content": true
    },
    {
      "id": 3,
      "// truncated content": true
    },
    {
      "id": 4,
      "// truncated content": true
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}

Example: 1st level and 2nd level component

当我们联表加载 2 层深度,同时请求 seo 组件以及嵌套在 seo 内部的 metaSocial 组件时,我们可以得到如下示例响应。

请注意,我们现在在响应中包含了与 metaSocial 组件相关的数据(见高亮行):

联表加载 seo 组件以及嵌套的 metaSocial 组件。

cURL

curl 'http://localhost:1337/api/articles?populate[0]=seo&populate[1]=seo.metaSocial' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles?populate[0]=seo&populate[1]=seo.metaSocial',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "documentId": "c2imt19iywk27hl2ftph7s",
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "…",
      "seo": {
        "id": 1,
        "documentId": "e8cnux5ejxyqrejd5addfv",
        "metaTitle": "Articles - FoodAdvisor",
        "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor",
        "keywords": "food",
        "metaRobots": null,
        "structuredData": null,
        "metaViewport": null,
        "canonicalURL": null,
        "metaSocial": [
          {
            "id": 1,
            "documentId": "ks7xsp9fewoi0qljcz9qa0",
            "socialNetwork": "Facebook",
            "title": "Browse our best articles about food and restaurants ",
            "description": "Discover our articles about food, restaurants, bars and more!"
          }
        ]
      }
    },
    {
      "id": 2,
      "// truncated content": true
    },
    {
      "id": 3,
      "// truncated content": true
    },
    {
      "id": 4,
      "// truncated content": true
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}

Populate dynamic zones

Dynamic zones are highly dynamic content structures by essence. When querying dynamic zones, standard populate parameters (such as populate[0]=dynamic-zone-name or populate=*) will only fetch the default scalar fields (e.g., strings, numbers, booleans) of the components within the dynamic zone. By default, they will not populate nested relations, media fields, or nested components inside those components.

To retrieve component-specific nested relations, media fields, or components within a dynamic zone, you must define per-component populate queries using the on property (fragment population syntax). This is because different components in a dynamic zone can have completely different structures, and require their own unique nested queries.

For instance, in the FoodAdvisor example application:

  • A blocks dynamic zone exists on the article content-type > **提示:**此处原本是一个交互式组件(ScreenshotNumberReference),在静态 Markdown 中无法渲染。可参考原始在线文档查看对应内容。.
  • The dynamic zone includes 3 different components: relatedArticles > **提示:**此处原本是一个交互式组件(ScreenshotNumberReference),在静态 Markdown 中无法渲染。可参考原始在线文档查看对应内容。, faq > **提示:**此处原本是一个交互式组件(ScreenshotNumberReference),在静态 Markdown 中无法渲染。可参考原始在线文档查看对应内容。, and CtaCommandLine > **提示:**此处原本是一个交互式组件(ScreenshotNumberReference),在静态 Markdown 中无法渲染。可参考原始在线文档查看对应内容。. All components have a different content structure containing various fields.
  • The relatedArticles component has an articles relation > **提示:**此处原本是一个交互式组件(ScreenshotNumberReference),在静态 Markdown 中无法渲染。可参考原始在线文档查看对应内容。 with the article content-type.

FoodAdvisor's 'blocks' dynamic zone structure in the Content-Type Builder

By default, none of the deeply nested fields or relations are included in the response of a GET request to /api/articles. With the appropriate populate parameters and by applying a detailed population strategy using the fragment on property, you can return precisely the data you need.

TIP

高级查询参数的语法手动构建起来可能相当复杂。我们建议使用我们的 interactive query builder(交互式查询构建器) 工具来生成 URL。例如,下面示例中使用的 /api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=* URL,就是通过使用我们的工具转换以下对象生成的:

{
  populate: {
    blocks: { // asking to populate the blocks dynamic zone
      on: { // using a detailed population strategy to explicitly define what you want
        'blocks.related-articles': {
          populate: {
           'articles': {
             populate: ['image']
           }
         }
        },
        'blocks.cta-command-line': {
          populate: '*'
        }
      },
    },
  },
}

Let's compare and explain the responses returned with some examples of a shared population strategy and a detailed population strategy:

Example

When we populate the blocks dynamic zone, we explicitly define which data to populate.

In the following example response, highlighted lines show that:

  • We deeply populate the articles relation of the relatedArticles component, and even the image media field of the related article using fragment (on) population.

  • But because we have only asked to populate everything for the CtaCommandLine component and have not defined anything for the faq component, no data from the faq component is returned.

使用逐组件的详细联表加载策略来联表加载 blocks 动态区域。

cURL

curl 'http://localhost:1337/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*' \
  -H 'Authorization: Bearer <token>'

JavaScript

const response = await fetch(
  'http://localhost:1337/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*',
  {
    headers: {
      Authorization: 'Bearer <token>',
    },
  }
);
const data = await response.json();

响应

{
  "data": [
    {
      "id": 1,
      "documentId": "it9bbhcgc6mcfsqas7h1dp",
      "title": "Here's why you have to try basque cuisine, according to a basque chef",
      "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
      "createdAt": "2021-11-09T13:33:19.948Z",
      "updatedAt": "2023-06-02T10:57:19.584Z",
      "publishedAt": "2022-09-22T09:30:00.208Z",
      "locale": "en",
      "ckeditor_content": "// truncated content",
      "blocks": [
        {
          "id": 2,
          "documentId": "e8cnux5ejxyqrejd5addfv",
          "__component": "blocks.related-articles",
          "articles": {
            "data": [
              {
                "id": 2,
                "documentId": "wkgojrcg5bkz8teqx1foz7",
                "title": "What are chinese hamburgers and why aren't you eating them?",
                "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
                "createdAt": "2021-11-11T13:33:19.948Z",
                "updatedAt": "2023-06-01T14:32:50.984Z",
                "publishedAt": "2022-09-22T12:36:48.312Z",
                "locale": "en",
                "ckeditor_content": "// truncated content",
                "image": {
                  "data": {
                      "// …": true
                    }
                  }
                }
              },
              {
                "id": 3,
                "// …": true
              },
              {
                "id": 4,
                "// …": true
              }
            ]
          }
        },
        {
          "id": 2,
          "__component": "blocks.cta-command-line",
          "theme": "primary",
          "title": "Want to give a try to a Strapi starter?",
          "text": "❤️",
          "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git"
        }
      ]
    },
    {
      "id": 2,
      "// …": true
    },
    {
      "id": 3,
      "documentId": "z5jnfvyuj07fogzh1kcbd3",
      "title": "7 Places worth visiting for the food alone",
      "slug": "7-places-worth-visiting-for-the-food-alone",
      "createdAt": "2021-11-12T13:33:19.948Z",
      "updatedAt": "2023-06-02T11:30:00.075Z",
      "publishedAt": "2023-06-02T11:30:00.075Z",
      "locale": "en",
      "ckeditor_content": "// truncated content",
      "blocks": [
        {
          "id": 1,
          "documentId": "ks7xsp9fewoi0qljcz9qa0",
          "__component": "blocks.related-articles",
          "articles": {
            "// …": true
          }
        },
        {
          "id": 1,
          "documentId": "c2imt19iywk27hl2ftph7s",
          "__component": "blocks.cta-command-line",
          "theme": "secondary",
          "title": "Want to give it a try with a brand new project?",
          "text": "Up & running in seconds 🚀",
          "commandLine": "npx create-strapi-app my-project --quickstart"
        }
      ]
    },
    {
      "id": 4,
      "// …": true
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 4
    }
  }
}
在生产环境中避免过度联表加载

使用 populate=* 或深度联表加载插件可能会产生不可预测的、代价高昂的数据库查询。在生产环境中,请始终显式联表加载,并将深度限制在 2-3 层。考虑使用路由级中间件(route-level middlewares)来集中管理联表加载逻辑。请参阅 Strapi 博客上的 Building High-Performance Strapi Applications(构建高性能 Strapi 应用)。