Parâmetros de consulta aparecem depois do caractere ? em uma URL e permitem representar filtros, paginação, ordenação, busca e opções de resposta. O módulo Query String no Node.js oferece funções para transformar esse trecho em objetos e converter objetos novamente em texto.
Embora a API moderna URLSearchParams seja recomendada para muitos projetos novos, o módulo node:querystring continua presente em aplicações legadas e integrações que dependem de regras específicas. Compreender as diferenças evita bugs com valores repetidos, espaços, codificação e objetos complexos.
Neste guia, você aprenderá a usar parse(), stringify(), escape() e unescape(), comparar o módulo com URLSearchParams, validar entrada, limitar parâmetros e evitar vulnerabilidades ou ambiguidades em APIs.
O que é uma query string?
Considere a URL:
https://example.com/products?category=books&page=2&sort=priceA query string é:
category=books&page=2&sort=priceA documentação oficial do módulo Query String descreve a API. Para a abordagem moderna, consulte a documentação de URLSearchParams na MDN.
O guia de URL API no Node.js mostra como analisar endereços completos. Para validar os valores resultantes, veja Zod no TypeScript.
Importando o módulo
const querystring = require('node:querystring');Em ES Modules:
import querystring from 'node:querystring';Convertendo texto em objeto
const values = querystring.parse(
'category=books&page=2&sort=price'
);
console.log(values);O resultado contém propriedades com strings:
{
category: 'books',
page: '2',
sort: 'price'
}O valor page não se torna número automaticamente. A aplicação precisa converter e validar.
Valores repetidos
const values = querystring.parse(
'tag=node&tag=javascript&tag=backend'
);Chaves repetidas podem resultar em array:
{
tag: ['node', 'javascript', 'backend']
}Sua API deve decidir se aceita um valor, vários valores ou rejeita repetição. Não use silenciosamente o primeiro ou o último sem documentar.
Parâmetros vazios
querystring.parse('search=&active')Parâmetros sem valor e valores vazios podem ser representados de forma semelhante. Diferencie ausência, string vazia e valor padrão de acordo com a regra de negócio.
Separadores personalizados
const values = querystring.parse(
'name:Ana;role:admin',
';',
':'
);O segundo argumento define o separador entre pares e o terceiro define o separador entre chave e valor. Esse recurso é útil em formatos legados, mas não deve ser usado para inventar protocolos ambíguos.
Limite de chaves
const values = querystring.parse(
input,
'&',
'=',
{ maxKeys: 100 }
);Limitar a quantidade de parâmetros reduz consumo de CPU e memória. Um cliente pode enviar milhares de pares para tentar degradar o serviço.
Convertendo objeto em texto
const text = querystring.stringify({
category: 'books',
page: 2,
sort: 'price'
});O resultado será semelhante a:
category=books&page=2&sort=priceObjetos aninhados não são representados automaticamente como JSON estruturado. Defina uma convenção explícita.
Arrays
const text = querystring.stringify({
tag: ['node', 'javascript']
});O módulo costuma gerar a chave repetida. Ao integrar com outro sistema, confirme se ele espera repetição, vírgulas, colchetes ou JSON.
Codificação de caracteres
querystring.stringify({
search: 'café com leite'
});Espaços e caracteres Unicode são codificados para transporte em URLs. Nunca concatene diretamente valores externos:
const unsafe = `search=${userInput}`;Use APIs de codificação para evitar que caracteres como & e = alterem a estrutura.
escape()
const encoded = querystring.escape('café & chá');escape() codifica um componente. Para código moderno e simples, encodeURIComponent() geralmente é mais familiar e interoperável.
unescape()
const decoded = querystring.unescape('caf%C3%A9');Decodificação pode falhar ou produzir resultado inesperado com entrada malformada. Trate erros e limite tamanho antes de processar grandes strings.
URLSearchParams
const params = new URLSearchParams({
category: 'books',
page: '2'
});
console.log(params.toString());URLSearchParams integra diretamente com a classe URL e funciona também em navegadores. Para projetos novos, costuma ser a melhor escolha.
Lendo parâmetros com URL
const url = new URL(
req.url,
'https://api.example.com'
);
const page = url.searchParams.get('page');
const tags = url.searchParams.getAll('tag');Essa abordagem evita separar manualmente caminho e query string.
Diferenças importantes
- Valores repetidos: URLSearchParams usa
getAll(). - Iteração: URLSearchParams oferece iteradores.
- Integração: URLSearchParams pertence à URL API.
- Objetos: querystring retorna objeto simples.
- Compatibilidade: sistemas legados podem depender da codificação do módulo antigo.
Não migre sem testes de caracteres, repetição e espaços.
Convertendo números
function parsePositiveInteger(value, fallback) {
const number = Number(value);
if (!Number.isInteger(number) || number < 1) {
return fallback;
}
return number;
}Nunca confie em coerção automática. Valores como string vazia, notação científica e números enormes podem produzir resultados inesperados.
Booleanos
function parseBoolean(value) {
if (value === 'true') return true;
if (value === 'false') return false;
return undefined;
}Boolean('false') retorna verdadeiro porque a string não está vazia. Faça uma conversão explícita.
Enumerações
const allowedSort = new Set([
'price',
'name',
'createdAt'
]);
if (!allowedSort.has(sort)) {
throw new Error('Ordenação inválida');
}Essa allowlist impede que o cliente escolha qualquer coluna ou expressão.
Paginação segura
const page = Math.min(
parsePositiveInteger(params.page, 1),
10000
);
const limit = Math.min(
parsePositiveInteger(params.limit, 20),
100
);Limites evitam respostas gigantes e consultas caras.
Filtros de banco
Parâmetros de consulta nunca devem ser interpolados diretamente em SQL:
const sql = `SELECT * FROM users WHERE name = '${name}'`;Use consultas parametrizadas e mapeie apenas filtros permitidos. Veja Pool PostgreSQL no Node.js para conexões e transações.
Prototype pollution
Objetos criados por parsers precisam ser tratados como entrada externa. Evite mesclar valores diretamente em objetos de configuração:
Object.assign(config, parsedInput);Use uma allowlist de chaves e construa um novo objeto. O comportamento de protótipo depende do parser e da versão, portanto defesa explícita é mais segura.
Objetos aninhados
Uma query como:
filter[name]=Ana&filter[role]=adminnão é automaticamente convertida em objeto aninhado pelo módulo nativo. Bibliotecas externas podem suportar essa sintaxe, mas aumentam a superfície de ataque e a complexidade.
Para APIs públicas, prefira parâmetros simples ou um corpo JSON validado.
Tamanho máximo
Servidores e proxies aplicam limites à linha da requisição. Mesmo antes desse limite, sua aplicação deve rejeitar queries enormes. Uma URL muito longa prejudica logs, caches e observabilidade.
Normalização para cache
Estas URLs podem representar a mesma consulta:
?page=2&sort=name
?sort=name&page=2Se a chave de cache usa a string bruta, elas geram entradas diferentes. Normalize parâmetros aceitos em uma ordem estável:
const normalized = new URLSearchParams();
normalized.set('page', String(page));
normalized.set('sort', sort);Assinaturas e webhooks
Ao verificar uma assinatura, não reconstrua a query sem confirmar o algoritmo. Mudanças de ordem, espaços e codificação podem alterar o valor assinado. Use os bytes originais quando o protocolo exigir.
O artigo de Webhooks Seguros com Node.js explica HMAC e comparação correta.
Logs
Não registre a URL completa sem sanitização. Queries podem conter tokens, e-mails, termos de busca e dados pessoais.
function sanitizeSearchParams(params) {
const safe = new URLSearchParams(params);
for (const key of ['token', 'key', 'email']) {
if (safe.has(key)) safe.set(key, '[REDACTED]');
}
return safe.toString();
}Testes
Cubra:
- valor único;
- valores repetidos;
- parâmetro vazio;
- Unicode;
- caracteres reservados;
- número inválido;
- booleano inválido;
- chave desconhecida;
- quantidade excessiva;
- URL muito longa.
Use o Node Test Runner para automatizar casos.
Exemplo de parser de API
function parseProductQuery(searchParams) {
const allowed = new Set([
'page',
'limit',
'category',
'sort'
]);
for (const key of searchParams.keys()) {
if (!allowed.has(key)) {
throw new Error(`Parâmetro desconhecido: ${key}`);
}
}
return {
page: parsePositiveInteger(
searchParams.get('page'),
1
),
limit: Math.min(
parsePositiveInteger(
searchParams.get('limit'),
20
),
100
),
category: searchParams.get('category') || undefined,
sort: searchParams.get('sort') || 'name'
};
}Erros comuns
- Confiar em tipos: todos os valores chegam como texto.
- Ignorar repetição: arrays podem ser tratados incorretamente.
- Concatenar manualmente: caracteres reservados quebram a URL.
- Permitir qualquer chave: filtros e ordenações perigosas entram na consulta.
- Registrar tokens: segredos aparecem nos logs.
- Migrar sem testes: codificação pode mudar.
- Aceitar query ilimitada: CPU, memória e logs são afetados.
Boas práticas
- Prefira URL e URLSearchParams em projetos novos.
- Valide tipos explicitamente.
- Use allowlist de chaves.
- Limite quantidade e tamanho.
- Controle paginação.
- Use consultas parametrizadas.
- Normalize chaves de cache.
- Sanitize logs.
- Teste repetição e Unicode.
- Documente a convenção para arrays.
Conclusão
O módulo Query String no Node.js permite converter parâmetros de consulta entre texto e objetos, sendo especialmente útil em sistemas legados e integrações com convenções específicas.
Em APIs modernas, URLSearchParams costuma oferecer uma interface mais integrada e previsível. Independentemente da ferramenta, parâmetros continuam sendo entrada não confiável: precisam de limites, conversão explícita, allowlists, consultas parametrizadas e logs sanitizados. Essas práticas transformam uma conveniência de URL em uma interface segura e consistente.




