Pular para o conteudo principal

Paginação, filtros e ordenação sem criar endpoints ruins

Como desenhar endpoints de listagem que continuem previsíveis quando o volume cresce, os filtros combinam e o cliente precisa navegar sem surpresa.

Andrews Ribeiro

Andrews Ribeiro

Founder & Engineer

Trilha

Trilha para entrevistas de senior full stack

Etapa 9 / 14

O problema

Quase toda API começa com uma listagem inocente.

Algo como:

GET /orders

No início funciona. Depois aparecem novas necessidades:

  • filtrar por status
  • ordenar por data
  • buscar por cliente
  • paginar sem trazer tudo
  • mostrar total de resultados

Se o time vai adicionando isso no improviso, o endpoint vira uma sacola de parametros sem contrato claro.

E o resultado costuma ser ruim dos dois lados:

  • o cliente não entende o que esperar
  • o banco paga a conta de consultas ruins

Modelo mental

Um endpoint de listagem precisa responder quatro perguntas com clareza:

  1. Qual conjunto de dados eu estou olhando?
  2. Qual recorte desse conjunto eu quero ver?
  3. Em que ordem esse recorte aparece?
  4. Como eu navego por partes sem me perder?

Se uma dessas respostas fica vaga, a API começa a gerar surpresa.

E listagem com surpresa vira bug muito fácil.

Quebrando o problema

Filtro precisa ter semântica clara

Filtro não e só “aceitar qualquer query param”.

Você precisa deixar claro:

  • quais campos podem ser filtrados
  • se o filtro e exato, por faixa ou por busca textual
  • como filtros se combinam
  • o que acontece quando o valor e inválido

Quando isso não esta claro, cada consumidor inventa sua própria expectativa.

Ordenação precisa ser estavel

Esse ponto passa despercebido até a paginação começar a falhar.

Se você ordena só por created_at, dois registros podem ter o mesmo valor.

Se novos itens entram entre uma pagina e outra, o cliente pode ver:

  • item repetido
  • item pulado
  • navegação inconsistente

Por isso ordenação de listagem costuma precisar de desempate.

Algo como:

  • created_at desc
  • id desc

Isso deixa a ordem deterministica.

Paginação não e só cortar em blocos

As duas estratégias mais comuns sao:

  • offset
  • cursor

Offset e simples:

?limit=20&offset=40

Funciona bem em listas menores, relatórios administrativos e cenarios em que o usuário quer ir para “pagina 3”.

Mas ele sofre mais quando:

  • a tabela esta muito grande
  • a ordenação e pesada
  • novos dados entram o tempo todo

Cursor costuma ser melhor quando a lista e viva e a ordem precisa continuar estavel:

?limit=20&cursor=eyJjcmVhdGVkX2F0Ijoi...

Ele não e magia. Só modela melhor a ideia de “continue daqui”.

Resposta precisa combinar com o custo

Nem toda listagem precisa devolver:

  • total
  • page_count
  • pagina numerada

Em alguns casos, calcular total exato em toda request custa caro demais.

Entao um contrato melhor pode ser:

  • lista de itens
  • next_cursor
  • has_more

Prometer menos, mas prometer algo confiavel.

Endpoint bom também se protege

Listagem aberta demais vira convite para abuso e regressao.

Vale limitar:

  • limit máximo
  • campos que podem ordenar
  • filtros permitidos
  • combinações muito caras

Isso não e antiproduto.

E parte do contrato.

Campo bonito para UI não deveria virar sort livre só porque existe na tela.

Ordenação exposta sem critério costuma ser só um jeito rápido de empurrar custo escondido para o banco.

Exemplo simples

Imagine um endpoint de pedidos:

GET /orders?status=paid&created_from=2026-03-01&sort=created_at:desc,id:desc&limit=20

Resposta:

{
  "items": [
    {
      "id": "ord_103",
      "status": "paid",
      "created_at": "2026-03-23T10:30:00Z"
    }
  ],
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMy0yM1QxMDozMDowMFoiLCJpZCI6Im9yZF8xMDMifQ==",
  "has_more": true
}

O que esse contrato deixa claro:

  • status e filtro exato
  • created_from define recorte temporal
  • a ordenação tem critério principal e desempate
  • a navegação continua por cursor

Agora compare com um endpoint confuso:

GET /orders?filter=paid&sort=recent&page=2

Aqui falta quase tudo:

  • filter filtra o que exatamente?
  • recent significa quanto?
  • page=2 depende de qual ordenação estavel?

O problema não e a sintaxe ser curta.

E o contrato ser fraco.

Erros comuns

  • Misturar busca textual, filtro exato e faixa no mesmo parametro genérico.
  • Ordenar por campo não estavel e depois culpar a paginação.
  • Expor qualquer campo para sort sem pensar em indice e custo.
  • Devolver total exato sempre, mesmo quando isso pesa demais.
  • Escolher offset ou cursor por moda, não pelo comportamento da lista.

Como um senior pensa

Quem tem mais experiência olha para listagem como contrato de produto e operação ao mesmo tempo.

O raciocínio costuma ser:

“Esse endpoint precisa ser previsivel para quem consome e sustentavel para quem opera.”

Isso muda a conversa.

Não e mais sobre “qual query param ficou mais bonito”.

E sobre:

  • semântica clara
  • ordenação deterministica
  • custo controlado
  • evolução sem quebrar cliente

O que o entrevistador quer ver

Em entrevista, esse tema aparece muito quando pedem para desenhar API de listagem.

O avaliador quer ver se você pensa alem do CRUD superficial.

Você sobe de nivel quando:

  • fala de ordem estavel antes da paginação quebrar
  • diferencia simplicidade de offset do comportamento mais estavel de cursor
  • menciona custo de total, sort livre e filtros sem indice
  • trata listagem como contrato previsivel, não como CRUD com maquiagem

Em listagem de API, clareza de contrato evita bug no cliente e surpresa no banco.

  • fala de ordenação estavel antes de falar de pagina
  • compara offset e cursor pelo contexto
  • menciona limite máximo e allowlist de sort
  • mostra preocupação com custo e consistência da resposta

Uma resposta forte costuma soar assim:

“Eu trataria listagem como contrato. Primeiro defino filtros claros, depois uma ordenação deterministica e só entao escolho entre offset ou cursor. Se a lista muda muito e cresce bastante, cursor costuma ser mais seguro.”

Endpoint de listagem ruim não falha só no banco. Ele falha na confiança de quem tenta usar a API sem adivinhar comportamento.

Resumo rápido

O que vale manter na cabeça

Checklist de pratica

Use isto ao responder

Você concluiu este artigo

Parte da trilha: Trilha para entrevistas de senior full stack (9/14)

Próximo artigo Circuit breaker na prática Artigo anterior Como desenhar integração com serviço terceiro

Continue explorando

Artigos relacionados