Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Full-Text Search no PostgreSQL com Node.js

Atualizado em: 23 de outubro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Full-Text Search PostgreSQL no Node.js permite criar busca textual com stemming, ranking, dicionários, operadores booleanos e índices, sem depender imediatamente de um serviço externo. Para catálogos, bases de conhecimento, documentação e painéis administrativos, a busca nativa pode oferecer boa relevância com operação simples.

O recurso transforma documentos em tsvector e consultas em tsquery. O PostgreSQL normaliza palavras conforme uma configuração linguística, remove termos pouco úteis e calcula correspondências. A aplicação Node.js envia parâmetros, controla filtros e apresenta resultados destacados.

Conceitos principais

  • to_tsvector: converte texto em lexemas pesquisáveis;
  • plainto_tsquery: transforma texto comum em consulta;
  • websearch_to_tsquery: aceita sintaxe parecida com busca web;
  • @@: verifica correspondência;
  • ts_rank e ts_rank_cd: calculam relevância;
  • ts_headline: produz trechos destacados.

Primeira consulta

const result = await pool.query(
  `SELECT id, titulo
   FROM artigos
   WHERE to_tsvector('portuguese',
     coalesce(titulo, '') || ' ' || coalesce(conteudo, '')
   ) @@ plainto_tsquery('portuguese', $1)`,
  [termo]
);

A configuração portuguese aplica regras linguísticas adequadas ao português. Confirme se o ambiente possui a configuração e teste palavras reais do domínio.

Coluna tsvector armazenada

Calcular o vetor em todas as linhas a cada busca custa CPU. Para tabelas grandes, mantenha uma coluna gerada.

ALTER TABLE artigos
ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
  setweight(to_tsvector('portuguese', coalesce(titulo, '')), 'A') ||
  setweight(to_tsvector('portuguese', coalesce(resumo, '')), 'B') ||
  setweight(to_tsvector('portuguese', coalesce(conteudo, '')), 'C')
) STORED;

Os pesos dão mais importância ao título e ao resumo.

Índice GIN

CREATE INDEX artigos_search_vector_gin
ON artigos USING gin (search_vector);

O índice GIN acelera o operador @@. Meça tamanho, custo de escrita e plano de execução. Consulte Índices PostgreSQL no Node.js.

Busca com ranking

const result = await pool.query(
  `WITH query AS (
     SELECT websearch_to_tsquery('portuguese', $1) AS q
   )
   SELECT a.id,
          a.titulo,
          ts_rank_cd(a.search_vector, query.q) AS rank
   FROM artigos a, query
   WHERE a.search_vector @@ query.q
   ORDER BY rank DESC, a.id DESC
   LIMIT $2`,
  [termo, 20]
);

Use uma ordenação secundária estável. Ranking sozinho pode empatar e produzir páginas inconsistentes.

Sintaxe web

websearch_to_tsquery permite frases entre aspas, exclusão com hífen e operadores semelhantes aos mecanismos de busca. Ele é apropriado para entrada de usuário porque não lança os mesmos erros de sintaxe de uma to_tsquery construída manualmente.

const query = 'node.js "worker threads" -browser';

Destaques seguros

SELECT ts_headline(
  'portuguese',
  conteudo,
  websearch_to_tsquery('portuguese', $1),
  'StartSel=<mark>, StopSel=</mark>, MaxFragments=2'
) AS trecho
FROM artigos
WHERE id = $2;

ts_headline produz texto com marcações, mas a aplicação ainda deve tratar o resultado conforme o contexto de saída. Não confie em HTML armazenado pelo usuário. Escape o conteúdo ou use uma estratégia de sanitização robusta.

Filtros relacionais

A busca textual funciona melhor quando combinada com colunas normais.

SELECT id, titulo
FROM artigos
WHERE tenant_id = $1
  AND status = 'published'
  AND categoria_id = $2
  AND search_vector @@ websearch_to_tsquery('portuguese', $3)
ORDER BY published_at DESC
LIMIT 30;

Inclua tenant e autorização em todas as consultas relevantes. A busca não deve expor documentos que o usuário não poderia consultar diretamente.

Prefixos

Autocompletar por prefixo pode usar :*, mas não concatene entrada livre na consulta.

function normalizarPrefixo(value) {
  return value
    .trim()
    .toLowerCase()
    .replace(/[^\p{L}\p{N}_-]+/gu, ' ')
    .split(/\s+/)
    .filter(Boolean)
    .slice(0, 5);
}

const termos = normalizarPrefixo(input);
const tsquery = termos.map((_, i) => `$${i + 1}:*`).join(' & ');

Para implementações complexas, prefira funções SQL próprias e parâmetros seguros. Também limite frequência e tamanho da entrada.

Sinônimos e vocabulário do domínio

Termos como “JS”, “JavaScript” e “ECMAScript” podem representar a mesma intenção. Há duas estratégias comuns:

  • expandir sinônimos na aplicação antes da consulta;
  • configurar dicionários do PostgreSQL.

Dicionários oferecem integração profunda, mas exigem operação cuidadosa. Expansão na aplicação é mais simples para conjuntos pequenos.

Busca sem acentos

A extensão unaccent pode ajudar usuários que digitam sem acentuação.

CREATE EXTENSION IF NOT EXISTS unaccent;

Combinar unaccent com vetores gerados exige funções imutáveis ou uma coluna atualizada por trigger. Teste o custo e não altere produção sem plano de migration.

Busca parcial e trigramas

Full-text search procura palavras e lexemas, não trechos arbitrários. Para nomes, códigos e erros de digitação, pg_trgm pode complementar a solução.

CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX artigos_titulo_trgm
ON artigos USING gin (titulo gin_trgm_ops);

Use trigramas de forma seletiva; índices podem crescer bastante.

Paginação

Resultados ordenados por relevância mudam quando o conteúdo muda. Para páginas estáveis, inclua rank arredondado e ID no cursor ou limite a navegação a uma janela curta.

ORDER BY rank DESC, id DESC

Consulte Cursor Pagination no Node.js.

Preparando a API

app.get('/search', async (req, res) => {
  const q = String(req.query.q ?? '').trim();

  if (q.length < 2 || q.length > 200) {
    return res.status(400).json({ error: 'Consulta inválida' });
  }

  const rows = await buscarArtigos({
    tenantId: req.auth.tenantId,
    query: q,
    limit: 20
  });

  res.json({ items: rows });
});

Aplique timeout, rate limiting e limite de resultados. Consultas caras podem ser usadas para esgotar CPU e I/O.

Observabilidade

Registre duração, quantidade de resultados, filtros e uma versão anonimizada ou hash da consulta quando privacidade exigir. Monitore buscas sem resultado, p95, hit rate de cache e planos lentos.

Use EXPLAIN (ANALYZE, BUFFERS) com consultas representativas e compare antes e depois de índices. Veja Query Optimization no Node.js.

Cache

Buscas populares podem ser armazenadas por poucos segundos. A chave deve incluir tenant, filtros, idioma, página e versão do índice. Não use cache compartilhado sem namespace de autorização.

Atualizações e consistência

Coluna gerada é atualizada na mesma transação da linha. Se a busca for alimentada por uma projeção separada, aceite consistência eventual e monitore atraso.

Testes

  • acentos e variações de palavras;
  • frases entre aspas;
  • termos excluídos;
  • documentos sem título ou resumo;
  • filtros de tenant;
  • ranking esperado;
  • HTML e caracteres especiais;
  • consultas longas;
  • paginação com empates;
  • plano usando GIN.

Erros comuns

  • calcular to_tsvector em toda busca;
  • não especificar configuração linguística;
  • construir tsquery por concatenação insegura;
  • ignorar autorização nos filtros;
  • usar ranking sem ordenação secundária;
  • mostrar headline sem sanitização;
  • esperar correção de erros ortográficos automaticamente;
  • criar índices sem medir.

Checklist

  • configuração de idioma testada;
  • vetor armazenado;
  • índice GIN medido;
  • consulta parametrizada;
  • filtros de acesso aplicados;
  • ranking e desempate definidos;
  • entrada limitada;
  • timeout configurado;
  • destaques seguros;
  • métricas de qualidade e desempenho.

Conclusão

Full-Text Search PostgreSQL no Node.js oferece uma base sólida para busca textual integrada ao banco. Ela combina relevância, filtros relacionais e transações sem adicionar outro serviço à arquitetura.

Armazene o vetor, use GIN, trate idioma e segurança, e acompanhe consultas sem resultado. Quando requisitos como busca fuzzy avançada, distribuição global ou bilhões de documentos surgirem, um mecanismo dedicado pode ser necessário. Até lá, a busca nativa costuma ser simples, econômica e eficiente.

Consulte a documentação oficial de Full-Text Search do PostgreSQL e a documentação de queries do node-postgres.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita