DevGuideTK
← Trilha Backend

Paginação, filtros e ordenação

Paginação por deslocamento ou por cursor, e por que a primeira degrada com tabelas grandes e repete itens quando chegam dados novos.

As decisões de uma rota de listagem são cinco: a forma de paginar, o formato do cursor, os limites, como filtrar e ordenar, e o que fazer com o total.

Deslocamento ou cursor

Deslocamento (OFFSET)Cursor
Custo da páginaCresce com a profundidade (214 ms no deslocamento 1 milhão, no teste)Constante (0,09 ms no mesmo ponto)
Itens novos entre páginasRepete ou pula itensNão repete nem pula
Pular para a página 37SimNão
"Página 3 de 120"Sim, com uma contagemNão diretamente
OrdenaçãoQualquer umaPrecisa de um índice que cubra a ordenação, com desempate único
ImplementaçãoTrivialUm pouco mais elaborada

Quando cada uma faz sentido:

SituaçãoEscolha
APIs públicas, integrações, sincronização de dadosCursor
Feeds, mensagens, eventos, listas que crescem sem pararCursor
Rolagem infinita em aplicativosCursor
Telas administrativas com números de página, sobre tabelas pequenasDeslocamento
Relatórios com saltos para páginas específicasDeslocamento, com limite de profundidade

Uma combinação comum: cursor como padrão da API, e deslocamento apenas em telas internas, com um limite de profundidade (por exemplo, no máximo 10 mil itens), além do qual a tela pede para refinar a busca.

O formato do cursor

DecisãoRecomendação
OpacoCodificar em base64 os valores de ordenação. O cliente devolve sem interpretar, e o servidor pode mudar o formato interno sem quebrar ninguém
PrecisãoOs valores com a mesma precisão com que o banco compara: horários com microssegundos, números grandes como texto
ValidaçãoUm cursor malformado ou adulterado responde 400, e nunca vai direto para a consulta
Assinatura, se necessárioAssinar o cursor com uma chave do servidor, quando os valores dentro dele não devem ser alterados pelo cliente
FiltrosUm cursor vale para os mesmos filtros e a mesma ordenação; incluir ambos no cursor, ou recusar a mudança, evita páginas inconsistentes

Limites

RegraPor quê
Tamanho padrão (ex.: 20)O cliente que não informa nada recebe uma página razoável
Tamanho máximo (ex.: 100)?limite=1000000 não pode virar uma consulta de um milhão de linhas
Buscar um item a mais do que o pedidoSaber se existe próxima página sem fazer outra consulta

Filtros e ordenação

GET /pedidos?status=pago&clienteId=42&criadoDesde=2026-03-01&ordem=-criadoEm&limite=20
DecisãoRecomendação
Nomes dos filtrosOs mesmos nomes dos campos da resposta
IntervalosSufixos claros: criadoDesde, criadoAte; ou totalMin, totalMax
OrdenaçãoUm parâmetro ordem com o campo e o sinal para decrescente (-criadoEm)
Campos permitidosUma lista fechada de campos filtráveis e ordenáveis, cada um com índice que o atenda
Valores inválidos400 com o parâmetro e o motivo, no formato de erro da API

A lista fechada é importante por dois motivos. Primeiro, desempenho: permitir ordenar por qualquer campo significa permitir consultas que nenhum índice atende, como mostra o tópico de índices. Segundo, segurança: montar o ORDER BY com o texto vindo do cliente é injeção de SQL, já que a ordenação não pode ser passada como parâmetro da consulta. Mapeie o valor recebido para uma lista de colunas conhecidas.

Para o cursor funcionar com ordenações diferentes, ele precisa guardar os valores da coluna escolhida e do desempate, e a consulta precisa de um índice para cada ordenação oferecida. Por isso as APIs que usam cursor costumam oferecer poucas ordenações.

O total

OpçãoCustoQuando usar
Não devolverNenhumFeeds, rolagem infinita, APIs de sincronização
COUNT(*) a cada páginaAlto: 67 ms em 2 milhões de linhas, no testeTabelas pequenas, ou com filtros muito seletivos
Contagem só sob pedido (?incluirTotal=true)Pago só por quem precisaTelas que exibem o total
Estimativa do bancoMuito baixo"Cerca de 2 milhões de resultados"
Contagem em cacheBaixoTotais que podem estar alguns minutos desatualizados

Resumo da decisão

  1. Cursor por padrão; deslocamento só em telas internas, com limite de profundidade.
  2. Desempate único na ordenação, e índice que a cubra.
  3. Cursor opaco, com a precisão do banco, validado ao chegar.
  4. Tamanho padrão e máximo em toda listagem.
  5. Filtros e ordenações de uma lista fechada, cada um com índice.
  6. Total só quando necessário, e de forma barata.

O próximo nível mostra a paginação por cursor nas cinco linguagens, executada contra o PostgreSQL percorrendo todas as páginas de uma tabela.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Desenhar a listagem da minha API

    Quero desenhar as rotas de listagem da minha API: paginação, filtros, ordenação e total.
    
    Pergunte qual banco eu uso, quais listas a API oferece, o tamanho esperado de cada uma e quem as consome (telas, integrações, sincronização).
    
    Para cada lista, recomende:
    1. Deslocamento ou cursor, e por quê.
    2. Os filtros e ordenações a oferecer, e os índices necessários.
    3. O tamanho padrão e o máximo.
    4. Se e como devolver o total.
    5. O formato da resposta e do cursor.
    
    Termine com a convenção que todas as listas da API devem seguir.
  • Migrar de OFFSET para cursor

    Minha API usa paginação por OFFSET e as páginas finais estão lentas. Quero migrar para cursor sem quebrar os clientes.
    
    Pergunte qual linguagem, banco e framework eu uso, e quem consome a API.
    
    Explique:
    1. Como medir o problema atual: tempo por profundidade de página.
    2. Como implementar o cursor ao lado do parâmetro atual, sem remover o antigo.
    3. Os índices necessários.
    4. Como comunicar a mudança aos clientes e limitar a profundidade do OFFSET.
    5. Como testar que a nova paginação percorre todos os itens sem repetir nem pular.
  • Filtros e ordenação seguros

    Quero implementar filtros e ordenação na minha API sem abrir brechas de segurança nem consultas lentas.
    
    Pergunte qual linguagem, framework e banco eu uso.
    
    Explique:
    1. Por que a ordenação não pode ser um parâmetro da consulta e como montar o ORDER BY a partir de uma lista fechada.
    2. Como validar filtros e devolver 400 com erros claros.
    3. Como garantir que cada combinação de filtro e ordenação tenha índice.
    4. Como lidar com filtros de texto parcial sem curinga no início.
    5. Como combinar filtros com a paginação por cursor.