Paginacao API: como implementar de forma eficiente
Paginacao API vai alem do limite e offset. Neste guia, voce aprende a escolher a estrategia certa, evitar erros de performance e construir endpoints que escalam. Inclui cursor, keyset e boas praticas para APIs REST.
Quando uma API retorna milhares de registros de uma vez, o cliente nao precisa de tudo. Ele precisa de uma pagina, depois de outra. Paginacao API e o mecanismo que organiza essa entrega. Sem ela, a resposta fica lenta, o servidor sobrecarrega e o cliente descarta a maior parte do payload. Este guia mostra como implementar paginacao de forma eficiente, cobrindo offset, cursor e keyset, com criterios objetivos para cada escolha.
Passo 1: Entenda o cenario antes de escolher o metodo
Nenhuma estrategia de paginacao e universalmente superior. Offset e cursor resolvem problemas diferentes. Offset, com page e limit, e simples de entender e debugar. O cliente pede ?page=3&limit=20 e recebe os registros 41 a 60. Funciona bem em tabelas pequenas e em paineis administrativos, onde o volume raramente passa de algumas dezenas de milhares de linhas.
Cursor, com ?cursor=abc123&limit=20, usa um identificador opaco que aponta para a posicao exata na lista. Nao ha pagina 3, ha um ponto de continuacao. Isso evita dois problemas classicos do offset: a degradacao de performance em consultas profundas e a inconsistencia quando novos registros entram entre uma requisicao e outra. O GitHub, por exemplo, documenta o uso de paginacao baseada em cursor em sua API REST, o que indica a preferencia por estabilidade em dados que mudam com frequencia.
Ressalva concreta: offset com ORDER BY em coluna nao indexada pode transformar uma consulta de 50 milissegundos em uma de 3 segundos. Cursor exige indice na coluna de ordenacao, mas mantem a latencia quase constante, independente da profundidade.
Passo 2: Defina os parametros de paginacao no contrato da API
O contrato precisa ser explicito. Parametros comuns: limit (tamanho da pagina) e offset ou cursor. Defina um limite maximo, como 100 por requisicao, e um valor padrao, como 20. Isso evita que um cliente peça 10 mil registros por engano e derrube o endpoint.
A resposta deve incluir metadados de paginacao: total, next_cursor ou next_page, e has_more. Sem eles, o cliente nao sabe se existem mais paginas. Um formato comum:
{ "data": [...], "pagination": { "total": 1050, "next_cursor": "eyJpZCI6MjB9", "has_more": true } }
Erro comum: omitir has_more. O cliente precisa fazer uma requisicao extra para descobrir que a lista acabou. Com has_more, ele para de buscar no momento certo, economizando banda e processamento.
Passo 3: Implemente offset pagination para casos simples
Offset e a implementacao mais direta. No SQL, seria algo como:
SELECT * FROM produtos ORDER BY id LIMIT 20 OFFSET 40;
No endpoint, o controller traduz page e limit em OFFSET e LIMIT. Use page = 1 como padrao, nao 0, para evitar confusao em logs e ferramentas de monitoramento.
Dica: inclua ORDER BY obrigatorio. Sem ele, a ordem dos registros e indefinida e a paginacao fica inconsistente entre requisicoes. Ordene por uma coluna unica, como id ou created_at com id como desempate.
Contraexemplo: se o cliente insere um registro novo entre a pagina 1 e a pagina 2, o offset desloca todos os registros seguintes. O item que estava na posicao 21 agora esta na 22, e a pagina 2 repete ou pula dados. Isso e aceitavel em listas estaticas, mas nao em feeds ou logs em tempo real.
Passo 4: Use cursor pagination para dados em movimento
Cursor pagination resolve o problema do deslocamento. Em vez de contar linhas, o servidor usa um valor opaco, geralmente um base64 do ultimo id ou created_at da pagina anterior. O cliente envia esse valor na proxima requisicao, e o servidor retorna os registros a partir daquele ponto.
SELECT * FROM produtos WHERE id > :cursor_id ORDER BY id LIMIT 20;
O cursor e estavel mesmo com insercao de novos registros. Se um item novo entra no topo, a proxima pagina continua de onde parou, sem repeticao. A performance tambem melhora: o banco usa o indice do WHERE em vez de contar linhas descartadas.
Dica: codifique o cursor em base64 para evitar que o cliente tente adivinhar a estrutura interna. Nunca exponha o id bruto como cursor, pois isso permite enumeracao de dados sensiveis.
Erro comum: usar cursor baseado em updated_at sem indice composto. Se a ordenacao for por updated_at e id, crie um indice em (updated_at, id). Sem isso, a consulta faz um scan completo, anulando o beneficio do cursor.
Passo 5: Considere keyset pagination para datasets estaveis
Keyset e uma variante do cursor que usa colunas de negocio como chave. Em vez de um identificador opaco, o cliente envia o valor da ultima coluna de ordenacao, como ?after=2024-01-15T10:30:00Z. Funciona bem quando a ordenacao e por data e o volume e moderado.
SELECT * FROM eventos WHERE created_at > :last_timestamp ORDER BY created_at LIMIT 20;
A vantagem e a transparencia: o parametro e legivel e facil de debugar. A desvantagem e a fragilidade com empates. Se dois registros tem o mesmo created_at, a paginacao pode pular um deles. Solucao: adicionar id como desempate no WHERE e no ORDER BY.
Ressalva: keyset nao oferece acesso a pagina arbitraria. O cliente so pode navegar para frente, nunca pular para a pagina 5. Para interfaces que precisam de numeracao, offset ainda e a escolha.
Passo 6: Adicione filtros e ordenacao sem quebrar a paginacao
Filtros e paginacao precisam ser combinados com cuidado. Quando o cliente aplica um filtro, o total deve refletir apenas os registros filtrados, nao o conjunto inteiro. Calcule o total com COUNT(*) na mesma consulta filtrada.
Erro comum: aplicar o filtro depois da paginacao. Se o servidor pagina primeiro e filtra depois, o cliente recebe paginas com menos itens do que o limit e nunca ve todos os resultados. Filtre antes, no WHERE, e pagina depois.
Ordenacao tambem afeta a paginacao. Se o cliente ordena por nome, o cursor precisa ser baseado em nome, nao em id. Caso contrario, a ordem muda entre paginas e o resultado fica inconsistente. Exija que o parametro sort seja acompanhado da coluna correspondente no cursor ou no offset.
Dica: documente os filtros aceitos e os valores padrao de ordenacao. Isso reduz chamadas de suporte e erros de integracao.
Passo 7: Proteja a API contra abuso e limites extremos
Paginacao exposta publicamente atrai abuso. Um cliente pode iterar todas as paginas de uma lista enorme, gerando carga desnecessaria. Defina limites rigorosos de limit e considere rate limiting por IP ou por chave de API.
Em APIs publicas, o total pode ser omitido para evitar vazamento de dados. Se a lista tem 2 milhoes de registros, o cliente nao precisa saber o numero exato. Retorne apenas has_more e next_cursor.
Contraexemplo: um endpoint de busca com offset ilimitado pode ser usado para extrair a base inteira de clientes, um registro por vez. Com limit maximo de 20 e rate limit de 10 requisicoes por minuto, a extracao levaria dias, inviabilizando o ataque.
Passo 8: Teste a paginacao sob carga e com dados reais
Teste com volume real, nao com 50 registros de desenvolvimento. Crie um dataset de pelo menos 100 mil linhas e meca a latencia das paginas 1, 50 e 200. Offset tende a degradar a partir da pagina 50; cursor deve manter a latencia estavel.
Use ferramentas como k6 ou Apache JMeter para simular clientes paginando simultaneamente. Observe o uso de CPU e o tempo de resposta. Se a consulta com offset profundo passa de 500 milissegundos, considere migrar para cursor.
Dica: registre o tempo de resposta por pagina em logs estruturados. Isso permite detectar degradacao antes que o usuario reclame.
Checklist final
- [ ] Escolhi o metodo com base no volume e na taxa de atualizacao dos dados
- [ ] Defini
limitmaximo e valor padrao no contrato - [ ] Inclui
total,next_cursorounext_page, ehas_morena resposta - [ ] Adicionei
ORDER BYcom coluna unica ou composta - [ ] Filtros aplicados antes da paginacao, com
COUNTcorreto - [ ] Cursor codificado em base64, sem expor dados internos
- [ ] Rate limit e limites de
limitconfigurados - [ ] Testes de carga com dataset grande e paginas profundas
FAQ
O que e paginacao em API?
Paginacao em API e a tecnica de dividir uma grande lista de resultados em blocos menores, chamados paginas. O cliente solicita uma pagina por vez usando parametros como page e limit ou cursor. Isso reduz o tamanho da resposta, melhora a performance e evita sobrecarga no servidor.
Qual a diferenca entre offset e cursor pagination?
Offset usa page e limit para pular registros, sendo simples mas lento em paginas profundas. Cursor usa um identificador opaco que marca a posicao exata, mantendo a performance estavel e evitando duplicatas quando novos dados sao inseridos. Offset e melhor para listas pequenas; cursor, para dados em tempo real.
Quando usar keyset pagination?
Keyset e indicado quando a ordenacao e por uma coluna natural, como data de criacao, e o volume de dados e moderado. Ele e transparente e facil de debugar, mas nao permite acesso a pagina arbitraria. Exige indice na coluna de ordenacao para funcionar bem.
Como evitar duplicatas na paginacao?
Duplicatas ocorrem quando novos registros sao inseridos entre requisicoes com offset. Use cursor ou keyset, que ancoram a posicao no ultimo item visto. Se precisar de offset, ordene por coluna unica e aceite a possibilidade de pequenas inconsistencias em dados muito dinamicos.
Qual o limite ideal de itens por pagina?
O limite ideal varia, mas valores entre 20 e 100 sao comuns. Defina um maximo, como 100, para evitar respostas gigantes. O valor padrao deve ser menor, por exemplo 20, para manter a latencia baixa e o payload enxuto.
O que fazer quando a API precisa de pagina arbitraria?
Se o cliente precisa acessar a pagina 5 diretamente, offset e a unica opcao pratica. Nesse caso, use ORDER BY indexado e limite o limit a 100. Para volumes acima de 100 mil registros, avalie se a pagina arbitraria e realmente necessaria ou se o cursor atende ao caso de uso.
A paginacao nao e um detalhe de implementacao. Ela define a experiencia de quem consome a API e a saude do servidor. Observe o comportamento dos dados, meca a performance e escolha o metodo que se ajusta ao cenario, nao ao mais popular. API que escala nao e a que retorna tudo, e a que entrega o suficiente, na hora certa.