URLSearchParams: pare de montar query string na mão

Query string parece simples até o momento em que uma busca aceita texto com espaço, filtros opcionais, paginação e mais de um valor para a mesma chave. Aí aparece aquele código com ?, &, condicionais e encodeURIComponent espalhados pelo caminho. Ele costuma funcionar no caso feliz. O problema começa quando alguém pesquisa por café & pão, um filtro não foi preenchido ou a API espera tag repetida.

URL e URLSearchParams são APIs nativas que tiram essa montagem manual da jogada. Elas não substituem a validação da aplicação nem fazem uma URL ruim virar uma boa API, mas dão um lugar correto para criar, ler e alterar parâmetros. É menos código defensivo e menos chance de a busca quebrar justamente quando o usuário escreve algo que não cabe num exemplo de documentação.

A concatenação manual acumula detalhes

Este padrão é familiar:

const url = "/api/produtos?q=" + encodeURIComponent(termo)
  + "&pagina=" + pagina
  + (categoria ? "&categoria=" + encodeURIComponent(categoria) : "");

Não é pecado, e numa URL estática pode ser suficiente. Em fluxos reais, porém, cada parâmetro opcional adiciona uma decisão. Também fica fácil esquecer o encoding em um valor, codificar duas vezes ou interpolar um dado que já veio codificado. Quando a URL tem origem conhecida, criar um objeto e alterar seus parâmetros deixa a intenção bem mais legível.

const url = new URL("/api/produtos", window.location.origin);

url.searchParams.set("q", termo);
url.searchParams.set("pagina", String(pagina));

if (categoria) {
  url.searchParams.set("categoria", categoria);
}

const response = await fetch(url);

O objeto cuida da serialização quando é usado pelo fetch ou convertido para texto. Você trabalha com os valores normais, sem precisar adivinhar onde o & entra ou como o espaço será representado. Para endpoints relativos no navegador, a origem atual serve como base; no servidor, use uma origem confiável e explícita em vez de montar a base a partir de entrada externa.

set, append e parâmetros repetidos

A escolha entre set() e append() é uma regra de contrato. set() mantém um único valor para a chave e substitui os anteriores. É o caso natural para página, ordenação ou termo de busca. append() acrescenta uma nova ocorrência e serve quando a API aceita filtros repetidos, como tag=javascript&tag=performance.

const params = new URLSearchParams();

params.set("ordenar", "recentes");

for (const tag of tagsSelecionadas) {
  params.append("tag", tag);
}

console.log(params.toString());
// ordenar=recentes&tag=javascript&tag=performance

Na leitura, get() retorna apenas o primeiro valor e getAll() retorna todos. Usar get() para uma chave repetível perde informação sem fazer barulho, um tipo de bug especialmente divertido porque a URL parece perfeita no DevTools.

Lendo filtros da URL atual

Para sincronizar estado de filtro com a URL da página, não é preciso escrever um parser. location.search contém a query string e pode alimentar URLSearchParams. A API remove o ? inicial, decodifica os valores e oferece has(), get() e getAll().

const params = new URLSearchParams(window.location.search);

const pagina = Number(params.get("pagina") ?? "1");
const termo = params.get("q") ?? "";
const tags = params.getAll("tag");

const filtros = {
  pagina: Number.isInteger(pagina) && pagina > 0 ? pagina : 1,
  termo,
  tags,
};

O exemplo ainda valida a página porque a query string é entrada do usuário, mesmo quando veio da própria interface há cinco segundos. URLSearchParams resolve representação e encoding; regras de domínio, limites e autorização continuam sendo responsabilidade da aplicação e do backend.

Dois cuidados que pegam de surpresa

  • Não passe uma URL completa para URLSearchParams. Ele interpreta a entrada como query string, não como URL. Para uma URL completa, crie primeiro new URL(...) e use url.searchParams.
  • Não faça interpolação dinâmica antes de criar os parâmetros. Valores com +, & ou = podem ganhar outro significado. Use append() ou set() com o valor bruto e deixe a API codificá-lo.

Há ainda uma diferença visual normal: na serialização de URLSearchParams, espaços aparecem como +; já algumas representações de URL usam %20. As duas são formas válidas de encoding, mas atualizar url.searchParams pode mudar a aparência da URL mesmo mantendo os mesmos parâmetros. Não compare query strings como texto se a regra de negócio precisa comparar valores.

Uma API nativa para um problema bem comum

URLSearchParams está disponível de forma ampla há anos e atende boa parte dos casos de filtros, busca e paginação. A troca vale principalmente quando a URL deixa de ser uma string fixa e passa a carregar estado: em vez de administrar separadores e escapes na mão, o código passa a declarar quais parâmetros existem e quais valores cada um recebe. É o tipo de ajuste que não rende uma demo chamativa, mas remove atrito de lugares onde bugs pequenos adoram se esconder.

Fontes