3 de Março de 2025
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
Founder & Engineer
4 min Intermediario Sistemas
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:
- Qual conjunto de dados eu estou olhando?
- Qual recorte desse conjunto eu quero ver?
- Em que ordem esse recorte aparece?
- 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 descid desc
Isso deixa a ordem deterministica.
Paginação não e só cortar em blocos
As duas estratégias mais comuns sao:
offsetcursor
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:
totalpage_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_cursorhas_more
Prometer menos, mas prometer algo confiavel.
Endpoint bom também se protege
Listagem aberta demais vira convite para abuso e regressao.
Vale limitar:
limitmá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:
statuse filtro exatocreated_fromdefine 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:
filterfiltra o que exatamente?recentsignifica quanto?page=2depende 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
sortsem pensar em indice e custo. - Devolver
totalexato sempre, mesmo quando isso pesa demais. - Escolher
offsetoucursorpor 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
offsetdo comportamento mais estavel decursor - menciona custo de
total,sortlivre 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
offsetecursorpelo 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
- Endpoint de listagem bom precisa deixar claro o que filtra, como ordena e como pagina.
- Ordenação sem critério estável vira lista com item repetido, item sumido e paginação confusa.
- Offset é simples, mas cursor costuma ser melhor para volume alto ou dados mudando o tempo todo.
- Limite máximo, sort allowlist e semântica explícita protegem tanto o banco quanto o cliente.
Checklist de pratica
Use isto ao responder
- Consigo explicar por que ordenação precisa ser determinística antes de paginar?
- Sei comparar offset e cursor sem transformar a conversa em dogma?
- Consigo propor filtros claros em vez de query params ambíguos?
- Sei falar de custo operacional ao desenhar endpoint de listagem?
Você concluiu este artigo
Parte da trilha: Trilha para entrevistas de senior full stack (9/14)
Próximo passo
Como modelar entidades Próximo passo →Compartilhar esta página
Copie o link manualmente no campo abaixo.