Skip to main content

Utilisation de la pagination dans l’API REST

Découvrez comment parcourir les réponses paginées de l’API REST.

À propos de la pagination

Lorsqu’une réponse de l’API REST inclut de nombreux résultats, GitHub les pagine et en retourne une partie. Par exemple, GET /repos/octocat/Spoon-Knife/issues ne retourne que 30 problèmes du dépôt octocat/Spoon-Knife, même si celui-ci compte plus de 1 600 problèmes ouverts. La réponse est ainsi plus facile à gérer pour les serveurs et pour les utilisateurs.

Vous pouvez utiliser l’en-tête link de lien de la réponse pour demander des pages de données supplémentaires. Si un point de terminaison prend en charge le paramètre de requête per_page, vous pouvez contrôler le nombre de résultats retournés sur une page.

Cet article montre comment demander des pages de résultats supplémentaires pour les réponses paginées, comment changer le nombre de résultats retournés sur chaque page et comment écrire un script pour récupérer plusieurs pages de résultats.

Quand une réponse est paginée, les en-têtes de réponse incluent un en-tête link. Si le point de terminaison ne prend pas en charge la pagination ou si tous les résultats rentrent sur une seule page, l'en-tête link sera omis.

L’en-tête link de lien contient des URL que vous pouvez utiliser pour récupérer des pages supplémentaires de résultats. Par exemple, la page précédente, la suivante, la première et la dernière page des résultats.

Pour afficher les en-têtes de réponse d’un point de terminaison particulier, vous pouvez utiliser curl, GitHub CLI ou une bibliothèque que vous utilisez pour effectuer des requêtes. Pour voir les en-têtes de réponse si vous utilisez une bibliothèque pour effectuer des demandes, suivez la documentation de la bibliothèque en question. Pour afficher les en-têtes de réponse si vous utilisez curl ou GitHub CLI, transmettez l’indicateur --include avec votre demande. Par exemple :

curl --include --request GET \
--url "https://api.github.com/repos/octocat/Spoon-Knife/issues" \
--header "Accept: application/vnd.github+json"

Si la réponse est paginée, l’en-tête link de lien ressemble à ceci :

link: <https://api.github.com/repositories/1300192/issues?page=2>; rel="prev", <https://api.github.com/repositories/1300192/issues?page=4>; rel="next", <https://api.github.com/repositories/1300192/issues?page=515>; rel="last", <https://api.github.com/repositories/1300192/issues?page=1>; rel="first"

L’en-tête link de lien fournit l’URL de la page précédente, de la page suivante, de la première page et de la dernière page des résultats :

  • L’URL de la page précédente est suivie de rel="prev".
  • L’URL de la page suivante est suivie de rel="next".
  • L’URL de la dernière page est suivie de rel="last".
  • L’URL de la première page est suivie de rel="first".

Dans certains cas, seule une partie de ces liens est disponible. Par exemple, le lien vers la page précédente n’est pas inclus si vous êtes sur la première page de résultats, et le lien vers la dernière page ne le sera pas non plus s’il ne peut pas être calculé.

Vous pouvez utiliser les URL de l’en-tête link de lien pour demander une autre page de résultats. Par exemple, pour demander la dernière page de résultats selon l’exemple précédent :

curl --include --request GET \
--url "https://api.github.com/repositories/1300192/issues?page=515" \
--header "Accept: application/vnd.github+json"

Les URL de l’en-tête link de lien utilisent des paramètres de requête pour indiquer la page de résultats à retourner. Les paramètres de requête dans les URL link de lien peuvent différer d’un point de terminaison à l’autre, mais chaque point de terminaison paginé utilisera les paramètres de requête page, before/after, ou since. (Certains points de terminaison utilisent le paramètre since pour autre chose que la pagination.) Dans tous les cas, vous pouvez utiliser les URL de l’en-tête link de lien pour récupérer des pages de résultats supplémentaires. Pour plus d’informations sur les paramètres de requête, consultez « Prise en main de l’API REST ».

Changement du nombre d’éléments par page

Si un point de terminaison prend en charge le paramètre de requête per_page, vous pouvez contrôler le nombre de résultats retournés sur une page. Pour plus d’informations sur les paramètres de requête, consultez « Prise en main de l’API REST ».

Par exemple, cette requête utilise le paramètre de requête per_page pour retourner deux éléments par page :

curl --include --request GET \
--url "https://api.github.com/repos/octocat/Spoon-Knife/issues?per_page=2" \
--header "Accept: application/vnd.github+json"

Le paramètre per_page est automatiquement inclus dans l’en-tête de lien link. Par exemple :

link: <https://api.github.com/repositories/1300192/issues?per_page=2&page=2>; rel="next", <https://api.github.com/repositories/1300192/issues?per_page=2&page=7715>; rel="last"

Écriture de scripts avec pagination

Au lieu de copier manuellement les URL à partir de l’en-tête link de lien, vous pouvez écrire un script pour récupérer plusieurs pages de résultats.

Les exemples suivants utilisent JavaScript et la bibliothèque Octokit.js de GitHub. Pour plus d’informations sur Octokit.js, consultez « Prise en main de l’API REST » et le fichier README d’Octokit.js.

Exemple utilisant la méthode de pagination Octokit.js

Pour récupérer des résultats paginés avec Octokit.js, vous pouvez utiliser octokit.paginate(). octokit.paginate() récupère la page de résultats suivante jusqu’à atteindre la dernière page, puis retourne tous les résultats sous la forme d’un tableau. Quelques points de terminaison retournent les résultats paginés sous forme de tableau dans un objet, au lieu de les retourner sous forme de tableau. octokit.paginate() retourne toujours un tableau d’éléments même si le résultat brut était un objet.

Par exemple, ce script obtient tous les problèmes du dépôt octocat/Spoon-Knife. Bien qu’elle demande 100 problèmes à la fois, la fonction n’est pas retournée tant que la dernière page de données n’est pas atteinte.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
});

const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "octocat",
  repo: "Spoon-Knife",
  per_page: 100,
  headers: {
    "X-GitHub-Api-Version": "2022-11-28",
  },
});

console.log(data)

Vous pouvez passer une fonction de mappage facultative à octokit.paginate() pour mettre fin à la pagination avant que la dernière page ne soit atteinte ou pour réduire l’utilisation de la mémoire en conservant uniquement une partie de la réponse. Vous pouvez également utiliser octokit.paginate.iterator() pour itérer au sein d’une seule page à la fois au lieu de demander chaque page. Pour plus d’informations, consultez la documentation d’Octokit.js.

Exemple de création d’une méthode de pagination

Si vous utilisez un autre langage ou une autre bibliothèque qui n’a pas de méthode de pagination, vous pouvez créer votre propre méthode de pagination. Cet exemple utilise quand même la bibliothèque Octokit.js pour faire des demandes, mais ne s’appuie pas sur octokit.paginate().

La fonction getPaginatedData fait une demande à un point de terminaison avec octokit.request(). Les données de la réponse sont traitées par parseData, qui gère les cas où aucune donnée n’est retournée ou les cas où les données retournées sont un objet au lieu d’un tableau. Les données traitées sont ensuite ajoutées à une liste qui contient toutes les données paginées collectées jusqu’ici. Si la réponse inclut un en-tête link de lien et si l’en-tête link de lien inclut un lien pour la page suivante, la fonction utilise un pattern RegEx (nextPattern) pour obtenir l’URL de la page suivante. La fonction répète ensuite les étapes précédentes, en utilisant désormais cette nouvelle URL. Une fois que l’en-tête link de lien n’inclut plus de lien vers la page suivante, tous les résultats sont retournés.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
});

async function getPaginatedData(url) {
  const nextPattern = /(?<=<)([\S]*)(?=>; rel="Next")/i;
  let pagesRemaining = true;
  let data = [];

  while (pagesRemaining) {
    const response = await octokit.request(`GET ${url}`, {
      per_page: 100,
      headers: {
        "X-GitHub-Api-Version":
          "2022-11-28",
      },
    });

    const parsedData = parseData(response.data)
    data = [...data, ...parsedData];

    const linkHeader = response.headers.link;

    pagesRemaining = linkHeader && linkHeader.includes(`rel=\"next\"`);

    if (pagesRemaining) {
      url = linkHeader.match(nextPattern)[0];
    }
  }

  return data;
}

function parseData(data) {
  // If the data is an array, return that
    if (Array.isArray(data)) {
      return data
    }

  // Some endpoints respond with 204 No Content instead of empty array
  //   when there is no data. In that case, return an empty array.
  if (!data) {
    return []
  }

  // Otherwise, the array of items that we want is in an object
  // Delete keys that don't include the array of items
  delete data.incomplete_results;
  delete data.repository_selection;
  delete data.total_count;
  // Pull out the array of items
  const namespaceKey = Object.keys(data)[0];
  data = data[namespaceKey];

  return data;
}

const data = await getPaginatedData("/repos/octocat/Spoon-Knife/issues");

console.log(data);