Comment implémenter la pagination dans les API REST (Guide étape par étape)

Lors de l'API REST, gérer les gros ensembles de données est crucial. La pagination est la solution standard pour diviser les données en "pages".

Louis Dupont

Louis Dupont

5 June 2025

Comment implémenter la pagination dans les API REST (Guide étape par étape)

Lors de la création d'API REST qui renvoient une liste de ressources, il est crucial de réfléchir à la manière de gérer les grands ensembles de données. Renvoyer des milliers, voire des millions d'enregistrements dans une seule réponse d'API est impraticable et peut entraîner des problèmes de performances importants, une consommation de mémoire élevée pour le serveur et le client, et une mauvaise expérience utilisateur. La pagination est la solution standard à ce problème. Elle consiste à diviser un grand ensemble de données en morceaux plus petits et gérables appelés « pages », qui sont ensuite servis de manière séquentielle. Ce tutoriel vous guidera à travers les étapes techniques de la mise en œuvre de diverses stratégies de pagination dans vos API REST.

💡
Vous voulez un excellent outil de test d'API qui génère une belle documentation d'API ?

Vous voulez une plateforme intégrée, tout-en-un, pour que votre équipe de développeurs travaille ensemble avec une productivité maximale ?

Apidog répond à toutes vos demandes et remplace Postman à un prix beaucoup plus abordable !
button

Pourquoi la pagination est-elle essentielle ?

Avant de plonger dans les détails de la mise en œuvre, abordons brièvement pourquoi la pagination est une fonctionnalité non négociable pour les API traitant des collections de ressources :

  1. Performance : la demande et le transfert de grandes quantités de données peuvent être lents. La pagination réduit la taille de la charge utile de chaque requête, ce qui entraîne des temps de réponse plus rapides et une réduction de la charge du serveur.
  2. Consommation de ressources : des réponses plus petites consomment moins de mémoire sur le serveur qui les génère et sur le client qui les analyse. Ceci est particulièrement important pour les clients mobiles ou les environnements aux ressources limitées.
  3. Limitation du débit et quotas : de nombreuses API appliquent des limites de débit. La pagination aide les clients à rester dans ces limites en récupérant les données par petits morceaux au fil du temps, plutôt que d'essayer de tout obtenir en une seule fois.
  4. Expérience utilisateur : pour les interfaces utilisateur qui consomment l'API, la présentation des données par pages est beaucoup plus conviviale que d'accabler les utilisateurs avec une liste énorme ou un défilement très long.
  5. Efficacité de la base de données : la récupération d'un sous-ensemble de données est généralement moins exigeante pour la base de données que la récupération d'une table entière, en particulier si une indexation appropriée est en place.

Stratégies de pagination courantes

Il existe plusieurs stratégies courantes pour la mise en œuvre de la pagination, chacune ayant ses propres compromis. Nous allons explorer les plus populaires : offset/limit (souvent appelé basé sur les pages) et basé sur le curseur (également connu sous le nom de pagination keyset ou seek).

1. Offset/Limit (ou pagination basée sur les pages)

Il s'agit sans doute de la méthode de pagination la plus simple et la plus largement adoptée. Elle fonctionne en permettant au client de spécifier deux paramètres principaux :

Alternativement, les clients peuvent spécifier :

Le offset peut être calculé à partir de page et pageSize en utilisant la formule : offset = (page - 1) * pageSize.

Étapes techniques de la mise en œuvre :

Supposons que nous ayons un point de terminaison d'API /items qui renvoie une liste d'éléments.

a. Paramètres de la requête API :
Le client effectuerait une requête comme :
GET /items?offset=20&limit=10 (récupérer 10 éléments, en ignorant les 20 premiers)
ou
GET /items?page=3&pageSize=10 (récupérer la 3e page, avec 10 éléments par page, ce qui équivaut à offset=20, limit=10).

Il est de bonne pratique de définir des valeurs par défaut pour ces paramètres (par exemple, limit=20, offset=0 ou page=1, pageSize=20) si le client ne les fournit pas. De plus, appliquez une limit ou pageSize maximale pour empêcher les clients de demander un nombre excessivement important d'enregistrements, ce qui pourrait solliciter le serveur.

b. Logique du backend (conceptuelle) :
Lorsque le serveur reçoit cette requête, il doit traduire ces paramètres en une requête de base de données.

// Exemple en Java avec Spring Boot
@GetMapping("/items")
public ResponseEntity<PaginatedResponse<Item>> getItems(
    @RequestParam(defaultValue = "0") int offset,
    @RequestParam(defaultValue = "20") int limit
) {
    // Valider la limite pour éviter les abus
    if (limit > 100) {
        limit = 100; // Appliquer une limite maximale
    }

    List<Item> items = itemRepository.findItemsWithOffsetLimit(offset, limit);
    long totalItems = itemRepository.countTotalItems(); // Pour les métadonnées

    // Construire et renvoyer une réponse paginée
    // ...
}

c. Requête de base de données (exemple SQL) :
La plupart des bases de données relationnelles prennent en charge directement les clauses offset et limit.

Pour PostgreSQL ou MySQL :

SELECT *
FROM items
ORDER BY created_at DESC -- L'ordre cohérent est crucial pour une pagination stable
LIMIT 10 -- Ceci est le paramètre 'limit'
OFFSET 20; -- Ceci est le paramètre 'offset'

Pour SQL Server (les versions antérieures peuvent utiliser ROW_NUMBER()) :

SELECT *
FROM items
ORDER BY created_at DESC
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;

Pour Oracle :

SELECT *
FROM (
    SELECT i.*, ROWNUM rnum
    FROM (
        SELECT *
        FROM items
        ORDER BY created_at DESC
    ) i
    WHERE ROWNUM <= 20 + 10 -- offset + limit
)
WHERE rnum > 20; -- offset

Remarque importante sur le tri : pour que la pagination offset/limit soit fiable, l'ensemble de données sous-jacent doit être trié par une clé cohérente et unique (ou presque unique), ou une combinaison de clés. Si l'ordre des éléments peut changer entre les requêtes (par exemple, de nouveaux éléments sont insérés ou des éléments sont mis à jour d'une manière qui affecte leur ordre de tri), les utilisateurs peuvent voir des éléments en double ou manquer des éléments lors de la navigation dans les pages. Un choix courant consiste à trier par horodatage de création ou par ID principal.

d. Structure de la réponse API :
Une bonne réponse paginée doit non seulement inclure les données de la page actuelle, mais également des métadonnées pour aider le client à naviguer.

{
  "data": [
    // tableau d'éléments pour la page actuelle
    { "id": "item_21", "name": "Item 21", ... },
    { "id": "item_22", "name": "Item 22", ... },
    // ... jusqu'à 'limit' éléments
    { "id": "item_30", "name": "Item 30", ... }
  ],
  "pagination": {
    "offset": 20,
    "limit": 10,
    "totalItems": 5000, // Nombre total d'éléments disponibles
    "totalPages": 500, // Calculé comme ceil(totalItems / limit)
    "currentPage": 3 // Calculé comme (offset / limit) + 1
  },
  "links": { // Liens HATEOAS pour la navigation
    "self": "/items?offset=20&limit=10",
    "first": "/items?offset=0&limit=10",
    "prev": "/items?offset=10&limit=10", // Null si sur la première page
    "next": "/items?offset=30&limit=10", // Null si sur la dernière page
    "last": "/items?offset=4990&limit=10"
  }
}

Fournir des liens HATEOAS (Hypermedia as the Engine of Application State) (self, first, prev, next, last) est une bonne pratique REST. Il permet aux clients de naviguer dans les pages sans avoir à construire eux-mêmes les URL.

Avantages de la pagination offset/limit :

Inconvénients de la pagination offset/limit :

2. Pagination basée sur le curseur (Keyset/Seek)

La pagination basée sur le curseur résout certaines des lacunes de offset/limit, en particulier les performances avec les grands ensembles de données et les problèmes de cohérence des données. Au lieu de s'appuyer sur un décalage absolu, elle utilise un « curseur » qui pointe vers un élément spécifique de l'ensemble de données. Le client demande ensuite des éléments « après » ou « avant » ce curseur.

Le curseur est généralement une chaîne opaque qui encode la ou les valeurs de la ou des clés de tri du dernier élément récupéré sur la page précédente.

Étapes techniques de la mise en œuvre :

a. Paramètres de la requête API :
Le client effectuerait une requête comme :
GET /items?limit=10 (pour la première page)
Et pour les pages suivantes :
GET /items?limit=10&after_cursor=opaquestringrepresentinglastitemid
Ou, pour paginer en arrière (moins courant mais possible) :
GET /items?limit=10&before_cursor=opaquestringrepresentingfirstitemid

Le paramètre limit définit toujours la taille de la page.

b. Qu'est-ce qu'un curseur ?
Un curseur doit être :

c. Logique du backend (conceptuelle) :

// Exemple en Java avec Spring Boot
@GetMapping("/items")
public ResponseEntity<CursorPaginatedResponse<Item>> getItems(
    @RequestParam(defaultValue = "20") int limit,
    @RequestParam(required = false) String afterCursor
) {
    // Valider la limite
    if (limit > 100) {
        limit = 100;
    }

    // Décoder le curseur pour obtenir les propriétés du dernier élément vu
    // par exemple, LastSeenItemDetails lastSeen = decodeCursor(afterCursor);
    // Si afterCursor est nul, c'est la première page.

    List<Item> items;
    if (afterCursor != null) {
        DecodedCursor decoded = decodeCursor(afterCursor); // par exemple, { lastId: "some_uuid", lastCreatedAt: "timestamp" }
        items = itemRepository.findItemsAfter(decoded.getLastCreatedAt(), decoded.getLastId(), limit);
    } else {
        items = itemRepository.findFirstPage(limit);
    }

    String nextCursor = null;
    if (!items.isEmpty() && items.size() == limit) {
        // En supposant que les éléments sont triés, le dernier élément de la liste est utilisé pour générer le curseur suivant
        Item lastItemOnPage = items.get(items.size() - 1);
        nextCursor = encodeCursor(lastItemOnPage.getCreatedAt(), lastItemOnPage.getId());
    }

    // Construire et renvoyer une réponse paginée par curseur
    // ...
}

// Méthodes d'assistance pour l'encodage/le décodage des curseurs
// private DecodedCursor decodeCursor(String cursor) { ... }
// private String encodeCursor(Timestamp createdAt, String id) { ... }

d. Requête de base de données (exemple SQL) :
L'essentiel est d'utiliser une clause WHERE qui filtre les enregistrements en fonction de la ou des clés de tri du curseur. La clause ORDER BY doit s'aligner sur la composition du curseur.

En supposant un tri par created_at (décroissant) puis par id (décroissant) comme critère de départage pour un ordre stable si created_at n'est pas unique :

Pour la première page :

SELECT *
FROM items
ORDER BY created_at DESC, id DESC
LIMIT 10;

Pour les pages suivantes, si le curseur est décodé en last_created_at_from_cursor et last_id_from_cursor :

SELECT *
FROM items
WHERE (created_at, id) < (CAST('last_created_at_from_cursor' AS TIMESTAMP), CAST('last_id_from_cursor' AS UUID)) -- Ou les types appropriés
-- Pour l'ordre croissant, ce serait >
-- La comparaison de tuple (created_at, id) < (val1, val2) est un moyen concis d'écrire :
-- WHERE created_at < 'last_created_at_from_cursor'
--    OR (created_at = 'last_created_at_from_cursor' AND id < 'last_id_from_cursor')
ORDER BY created_at DESC, id DESC
LIMIT 10;

Ce type de requête est très efficace, surtout s'il existe un index sur (created_at, id). La base de données peut directement « rechercher » le point de départ sans analyser les lignes non pertinentes.

e. Structure de la réponse API :

{
  "data": [
    // tableau d'éléments pour la page actuelle
    { "id": "item_N", "createdAt": "2023-10-27T10:05:00Z", ... },
    // ... jusqu'à 'limit' éléments
    { "id": "item_M", "createdAt": "2023-10-27T10:00:00Z", ... }
  ],
  "pagination": {
    "limit": 10,
    "hasNextPage": true, // booléen indiquant s'il y a plus de données
    "nextCursor": "base64encodedcursorstringforitem_M" // chaîne opaque
    // Potentiellement un "prevCursor" si les curseurs bidirectionnels sont pris en charge
  },
  "links": {
    "self": "/items?limit=10&after_cursor=current_request_cursor_if_any",
    "next": "/items?limit=10&after_cursor=base64encodedcursorstringforitem_M" // Null s'il n'y a pas de page suivante
  }
}

Notez que la pagination basée sur le curseur ne fournit généralement pas totalPages ou totalItems car le calcul de ceux-ci nécessiterait une analyse complète de la table, ce qui annule certains des avantages en termes de performances. Si ceux-ci sont strictement nécessaires, un point de terminaison distinct ou une estimation peuvent être fournis.

Avantages de la pagination basée sur le curseur :

Inconvénients de la pagination basée sur le curseur :

Choisir la bonne stratégie

Le choix entre offset/limit et la pagination basée sur le curseur dépend de vos exigences spécifiques :

Dans certains systèmes, une approche hybride est même utilisée, ou différentes stratégies sont proposées pour différents cas d'utilisation ou points de terminaison.

Meilleures pratiques pour la mise en œuvre de la pagination

Quelle que soit la stratégie choisie, respectez ces bonnes pratiques :

  1. Nommage cohérent des paramètres : utilisez des noms clairs et cohérents pour vos paramètres de pagination (par exemple, limit, offset, page, pageSize, after_cursor, before_cursor). Tenez-vous-en à une convention (par exemple, camelCase ou snake_case) tout au long de votre API.
  2. Fournir des liens de navigation (HATEOAS) : comme indiqué dans les exemples de réponses, incluez des liens pour self, next, prev, first et last (le cas échéant). Cela rend l'API plus détectable et découple le client de la logique de construction d'URL.
  3. Valeurs par défaut et limites maximales :
  1. Documentation API claire : documentez votre stratégie de pagination en détail :
  1. Tri cohérent : assurez-vous que les données sous-jacentes sont triées de manière cohérente pour chaque requête paginée. Pour offset/limit, cela est essentiel pour éviter le décalage des données. Pour la pagination basée sur le curseur, l'ordre de tri dicte la façon dont les curseurs sont construits et interprétés. Utilisez une colonne de départage unique (comme un ID principal) si la colonne de tri principale peut avoir des valeurs en double.
  2. Gérer les cas limites :
  1. Considérations relatives au nombre total :
  1. Gestion des erreurs : renvoyez les codes d'état HTTP appropriés pour les erreurs (par exemple, 400 pour une mauvaise entrée, 500 pour les erreurs du serveur lors de la récupération des données).
  2. Sécurité : bien que ce ne soit pas directement un mécanisme de pagination, assurez-vous que les données paginées respectent les règles d'autorisation. Un utilisateur ne doit pouvoir paginer que les données qu'il est autorisé à voir.
  3. Mise en cache : les réponses paginées peuvent souvent être mises en cache. Pour la pagination basée sur le décalage, GET /items?page=2&pageSize=10 est hautement cachable. Pour la pagination basée sur le curseur, GET /items?limit=10&after_cursor=XYZ est également cachable. Assurez-vous que votre stratégie de mise en cache fonctionne bien avec la façon dont les liens de pagination sont générés et consommés. Les stratégies d'invalidation doivent être prises en compte si les données sous-jacentes changent fréquemment.

Rubriques avancées (brèves mentions)

Conclusion

La mise en œuvre correcte de la pagination est essentielle pour la création d'API REST évolutives et conviviales. Bien que la pagination offset/limit soit plus simple pour commencer, la pagination basée sur le curseur offre des performances et une cohérence supérieures pour les grands ensembles de données dynamiques. En comprenant les détails techniques de chaque stratégie, en choisissant celle qui correspond le mieux aux besoins de votre application et en suivant les meilleures pratiques pour la mise en œuvre et la conception d'API, vous pouvez vous assurer que votre API fournit efficacement des données à vos clients, quelle que soit l'échelle. N'oubliez pas de toujours donner la priorité à une documentation claire et à une gestion robuste des erreurs pour offrir une expérience fluide aux consommateurs d'API.


Explore more

Fathom-R1-14B : Modèle de raisonnement IA avancé d'Inde

Fathom-R1-14B : Modèle de raisonnement IA avancé d'Inde

L'IA en expansion rapide. Fathom-R1-14B (14,8 milliards de paramètres) excelle en raisonnement mathématique et général, conçu par Fractal AI Research.

5 June 2025

Mistral Code : L'assistant de codage le plus personnalisable basé sur l'IA pour les entreprises

Mistral Code : L'assistant de codage le plus personnalisable basé sur l'IA pour les entreprises

Découvrez Mistral Code, l'IA d'aide au code la plus personnalisable pour les entreprises.

5 June 2025

Comment Claude Code transforme le codage de l'IA en 2025

Comment Claude Code transforme le codage de l'IA en 2025

Découvrez Claude Code en 2025 : codage IA révolutionné. Fonctionnalités, démo, et pourquoi il gagne du terrain après Windsurf d'Anthropic. Indispensable !

5 June 2025

Pratiquez le Design-first d'API dans Apidog

Découvrez une manière plus simple de créer et utiliser des API