Cuando una API comienza a manejar miles o millones de registros, es necesario establecer mecanismos para consultar, navegar y organizar los datos eficientemente. Sin estas prácticas, las APIs pueden volverse lentas, difíciles de usar o incluso peligrosas para el rendimiento del sistema.
En este artículo veremos cómo implementar paginación, filtros y ordenamientos en una API REST, con buenas prácticas, convenciones claras y ejemplos reales que puedes aplicar de inmediato.
¿Por qué necesitamos estas técnicas?
Sin paginación ni filtros:
- Un
GET /usuariospuede traer decenas de miles de registros en una sola respuesta. - Las bases de datos pueden colapsar bajo carga innecesaria.
- Los clientes (apps web, móviles, sistemas externos) se ven obligados a procesar y filtrar en el frontend, generando cuellos de botella.
Estas técnicas permiten:
- Reducir la carga del servidor
- Optimizar la experiencia de usuario
- Controlar la visibilidad de los datos
- Mejorar el rendimiento y la estabilidad de la API
Pagination: Dividiendo grandes volúmenes de datos
Cuando una API entrega listas de recursos —como usuarios, productos o facturas—, es muy probable que la colección crezca con el tiempo. Si cada solicitud retorna todos los elementos disponibles, se generan problemas de rendimiento, tanto en el servidor como en el cliente: más memoria, más tiempo de respuesta y una carga innecesaria de datos.
Para evitar esto, se utiliza la paginación, una técnica que permite dividir los resultados en bloques o “páginas” más pequeños y manejables. Así, el cliente puede navegar por partes específicas de la colección en lugar de cargarla completa.
Estrategia común: paginación por offset y limit
GET /clientes?page=2&limit=10
Convención:
-
page: número de página (comienza en 1 o 0, según la convención del equipo) -
limit: cantidad de elementos por página
Respuesta ejemplo:
{
"data": [ /* lista de clientes */ ],
"pagination": {
"page": 2,
"limit": 10,
"totalPages": 5,
"totalItems": 47
}
}
Esta estructura permite que el cliente frontend construya botones de "siguiente", "anterior", y vea cuántas páginas existen.
Cursor-based pagination
La paginación basada en cursores es una estrategia alternativa al tradicional offset/limit, y está diseñada para manejar colecciones grandes y datos que cambian constantemente, como timelines, feeds o sistemas de mensajería.
En lugar de indicar "dame la página 3", esta técnica utiliza un cursor que actúa como un marcador del último elemento recibido, permitiendo a la API retornar los siguientes resultados a partir de ese punto.
Por ejemplo:
En este caso, comment_987 es el ID del último comentario recibido previamente, y la API responderá con los siguientes 20 comentarios ordenados cronológicamente después de ese ID.
Ventajas:
- Más eficiente para grandes volúmenes de datos, ya que evita los
OFFSET, que pueden ser costosos en bases de datos relacionales. - Consistencia temporal: no hay riesgo de que un nuevo dato agregado altere el orden o los resultados de la página.
- Mejor rendimiento en bases de datos indexadas por ID o timestamp.
Desventajas:
- Es más complejo de implementar, especialmente si el ordenamiento no es lineal o si se requiere paginación bidireccional (adelante y atrás).
- No es ideal cuando se necesita acceder directamente a una página específica (ej. “ir a la página 5”).
Cuándo usar cursor-based pagination:
- Cuando trabajas con streams de datos en tiempo real
- Cuando los datos se modifican o crecen constantemente
- Cuando necesitas escalabilidad para miles o millones de resultados
???? Recomendación: si usas esta técnica, asegúrate de ordenar los resultados por un campo único, como
createdAtoid, y utiliza cursors que puedan ser reproducidos fácilmente (createdAt=2024-04-13T10:00:00Zoid=1234).
Esta estrategia es ampliamente utilizada por plataformas como Twitter, GitHub y Facebook, donde los datos son dinámicos y el rendimiento importa tanto como la precisión de la experiencia del usuario.
Paginación basada en timestamps
La paginación basada en timestamps es una variante de la paginación por cursor que se utiliza cuando los recursos tienen un campo temporal bien definido, como createdAt, updatedAt o fechaPublicacion. En lugar de utilizar un ID como cursor, esta técnica se basa en la marca de tiempo del último recurso recibido para solicitar los siguientes.
Por ejemplo:
GET /transacciones?limit=20&after=2024-04-01T12:00:00Z
Este endpoint le indica a la API que debe entregar las próximas 20 transacciones creadas después del timestamp especificado.
Ventajas:
- Es intuitiva y fácil de implementar si los datos ya están ordenados por fecha.
- Funciona bien para APIs que entregan datos cronológicos, como logs, noticias, transacciones o eventos.
- Puede ser combinada fácilmente con índices en bases de datos para obtener un buen rendimiento.
Desventajas:
- No garantiza unicidad si varios registros comparten el mismo timestamp (solución: combinar con un ID o usar un timestamp más preciso).
- No permite paginación inversa fácilmente (es decir, hacia atrás), salvo que la API lo implemente explícitamente.
Recomendaciones:
- Asegúrate de que el campo temporal utilizado tenga una resolución suficiente (idealmente milisegundos).
- Usa formato ISO 8601 UTC (
YYYY-MM-DDTHH:mm:ssZ) para máxima interoperabilidad. - Siempre ordena los resultados de forma explícita por ese campo (
ORDER BY createdAt ASC).
Esta técnica es ideal para APIs donde los datos crecen en el tiempo y los consumidores solo necesitan ver lo más nuevo, como:
- Registros de actividad (
/logs) - Publicaciones (
/posts) - Transacciones financieras (
/movimientos) - Notificaciones (
/notificaciones)
2. Filtering
Convención común:
GET /productos?categoria=ropa&disponible=true
Reglas prácticas:
-
Usa múltiples parámetros para distintos campos
-
Soporta múltiples valores:
?estado=activo,inactivo -
Usa operadores si es necesario:
?precioMin=100&precioMax=500
Ejemplo:
GET /ordenes?fechaInicio=2024-01-01&fechaFin=2024-01-31&estado=pagada
Recomendación:
-
Documenta los filtros disponibles en Swagger u OpenAPI
-
Usa nombres consistentes (
fechaInicio,precioMin,activo) y evita traducciones ambiguas
3. Sorting
Convención sugerida:
GET /articulos?sort=-fechaPublicacion
-
sort=nombre→ orden ascendente (A-Z, menor a mayor) -
sort=-fecha→ orden descendente
También puedes combinar:
GET /clientes?sort=apellido,-fechaAlta
Consejo: si tu backend usa SQL, esto se traduce fácilmente a ORDER BY apellido ASC, fecha_alta DESC.
Diseño de respuesta con paginación, filtros y orden
GET /usuarios?rol=admin&estado=activo&page=1&limit=20&sort=-fechaAlta
Respuesta ejemplo:
{
"data": [ /* lista de usuarios */ ],
"pagination": {
"page": 1,
"limit": 20,
"totalPages": 5,
"totalItems": 97
},
"sort": "-fechaAlta",
"filters": {
"rol": "admin",
"estado": "activo"
}
}
Esto le da al consumidor información contextual para construir interfaces inteligentes o integraciones externas confiables.
Buenas prácticas generales
- Valida los parámetros: evita que
limit=100000colapse tu API - Define un límite máximo razonable (ej:
limit <= 100) - Ofrece valores por defecto seguros:
page=1,limit=20,sort=fecha - Haz que las respuestas sean consistentes: no mezcles estructuras distintas en distintos endpoints
- Evita mezclar paginación por offset y por cursor en la misma API
Conclusión
Implementar paginación, filtrado y ordenamiento en tus APIs es mucho más que una cuestión técnica: es una forma de dar poder, eficiencia y control a quienes usan tu API. Una buena estrategia en estas áreas mejora la usabilidad, el rendimiento y la escalabilidad de tu sistema.
Si estás construyendo una API profesional, asegúrate de que tus endpoints ofrezcan estas capacidades desde el principio. Tus usuarios —humanos o máquinas— lo agradecerán.
