La forma más común de paginar datos desde una base de datos relacional.
Contexto
Cuando tienes una tabla con cientos o miles de registros, cargarlos todos de golpe es ineficiente. LIMIT controla cuántos resultados devolver y OFFSET cuántos saltar antes de empezar.
Consulta SQL con paginación
-- Página 1: registros 1 a 10
SELECT * FROM usuarios
ORDER BY id ASC
LIMIT 10 OFFSET 0;
-- Página 2: registros 11 a 20
SELECT * FROM usuarios
ORDER BY id ASC
LIMIT 10 OFFSET 10;
-- Página 3: registros 21 a 30
SELECT * FROM usuarios
ORDER BY id ASC
LIMIT 10 OFFSET 20;
La fórmula es simple: OFFSET = (page - 1) * limit.
Endpoint completo con paginación en Express.js
const express = require('express');
const mysql = require('mysql2/promise');
const app = express();
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: '',
database: 'tienda',
waitForConnections: true
});
// GET /api/usuarios?page=1&limit=10
app.get('/api/usuarios', async (req, res) => {
try {
// Parámetros con valores por defecto
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 10));
const offset = (page - 1) * limit;
// Obtener total de registros
const [countResult] = await pool.query(
'SELECT COUNT(*) AS total FROM usuarios'
);
const total = countResult[0].total;
// Obtener datos paginados
const [rows] = await pool.query(
'SELECT * FROM usuarios ORDER BY id ASC LIMIT ? OFFSET ?',
[limit, offset]
);
// Respuesta con metadatos
res.json({
data: rows,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('Servidor corriendo en http://localhost:3000');
});
Explicación paso a paso
- Parámetros de entrada:
pageylimitvienen de la query string, con validación para evitar valores negativos o límites excesivos. - Cálculo de OFFSET:
(page - 1) * limitconvierte página 1 en OFFSET 0, página 2 en OFFSET 10, etc. - Consulta de total: se necesita un
COUNT(*)aparte para calcular el número de páginas. - Consulta paginada:
LIMIT ? OFFSET ?con parámetros escapados evita inyección SQL. - Metadatos: se devuelven
page,limit,totalytotalPagespara que el frontend sepa cuántas páginas hay.
Tip extra: paginación con cursor (alternativa para datos en tiempo real)
-- En lugar de OFFSET, usas el último ID visto
SELECT * FROM usuarios
WHERE id > ? -- último ID de la página anterior
ORDER BY id ASC
LIMIT 10;
Esta variante es más eficiente en tablas grandes y evita duplicados cuando se insertan registros nuevos entre consultas.
La paginación con LIMIT/OFFSET es simple, funciona y es el estándar: úsala mientras tu volumen de datos no exija cursores.