Tradicionalmente hay 2 maneras de paginar una respuesta de una API, y ambas están estrechamente relacionadas con la paginación de una consulta a la base de datos:

  • Saltar la cantidad de elementos correspondientes a las páginas anteriores.
  • Incluír la condición de que los elementos han de ser mayores que el último, según algún criterio de ordenación.

El primer sistema es el más fácil de programar, y posiblemente el más usado, aunque falla cuando a la tabla/colección se le añaden filas/documentos por el principio de la consulta.‌‌El segundo es más eficiente, pero falla cuando el criterio de ordenación hace posible la existencia de dos documentos de igual orden.

Existe una tercera forma de paginar que es aún más eficiente, y que minimiza las dificultades que sufren los sistemas clásicos: Cachear el cursor.

La idea consiste en:

  1. No limitar el tamaño de la respuesta de la consulta a la base de datos.
  2. Obtener del cursor la cantidad de documentos/filas necesaria
  3. Cachear el cursor
  4. Cuando haya que cargar la siguiente página, obtener del cursor la cantidad de documentos/filas necesaria

Los retos para que este sistema funcione son:

  • El endpoint de paginación ha de usar el cursor correcto.
  • Hay que limpiar los cursores sin uso.
  • El endpoint de paginación ha de ser capaz de realizar una consulta a la base de datos, para crear un cursor nuevo, si el cursor requerido ha sido eliminado por falta de uso.

Hay muchas maneras de solventar dichos retos, algunas de las cuales son muy dependientes de las características del sistema. Por ejemplo si se tiene un servidor con estado se puede usar la sessión.

La solución que expondré está basada en el principio HATEOAS, sirve para servidores sin estado, usa un único endpoint genérico para gestionar todas las paginaciones de todos los endpoints,  y permite una gran generalización en los endpoints que devuelven la primer página.

La respuesta del endpoint que devuelve la primer página, y del endpoint genérico que devuelve todas las siguientes tiene dos campos:

  • docs: el array con los documentos
  • next: la url que hay que llamar para obtener la siguiente página

Dicha url incluye un token encriptado que contiene toda la información necesaria para identificar el cursor, para crear un nuevo cursor en caso necesario, para obtener la siguiente página del cursor, y algunos parametros extra para cuestiones de seguridad.

Por ejemplo, para mongo.

  • key: un string aleatorio que identifica al cursor, por ejemplo un uuidv4
  • userId: para comprobar que el usuario que realiza la petición es el mismo para el que se creo el token
  • size: el tamaño de la página a devolver
  • collection: nombre de la coleccion a usar
  • query: objecto con la consulta
  • options: objecto con la projección y otras opciones
  • sort: para ordenar el cursor
  • size: tamaño de la página
  • skip: elementos ya devueltos

En resumen, este sistema es más rápido, más eficiente, más amable con la base de datos, y resuelve la paginación, incuida la primer página, de forma muy genérica, con lo que también se ahorra tiempo de desarrollo y de testing.