O Meilisearch no Node.js adiciona busca rápida com tolerância a erros de digitação, filtros, facetas e ranking configurável. Ele é útil para catálogos, documentação, marketplaces, painéis administrativos e qualquer interface em que o usuário espera resultados enquanto digita.
Meilisearch não deve ser tratado como fonte principal de dados. PostgreSQL ou outro banco continua responsável por transações e consistência. O índice é uma projeção otimizada para leitura, atualizada por jobs, eventos ou Outbox Pattern.
Neste guia, você aprenderá a usar o SDK JavaScript, criar índices, enviar documentos em lotes, aguardar tasks, configurar searchable e filterable attributes, aplicar filtros por tenant, paginar, destacar resultados e manter o índice sincronizado com segurança.
O que é Meilisearch?
A documentação oficial de primeiros passos do Meilisearch apresenta índices como estruturas que recebem documentos e atendem consultas por API. O projeto pode ser usado na nuvem ou self-hosted.
O SDK JavaScript está documentado em Meilisearch API.
Instalação
npm install meilisearchCriando o cliente
import { MeiliSearch } from 'meilisearch';
export const search = new MeiliSearch({
host: process.env.MEILISEARCH_HOST ?? 'http://localhost:7700',
apiKey: process.env.MEILISEARCH_API_KEY
});Use uma key administrativa somente no backend. O navegador deve receber uma search key restrita, nunca a master key.
Criando um índice
const task = await search.createIndex('products', {
primaryKey: 'id'
});
await search.waitForTask(task.taskUid);Operações de configuração e indexação são assíncronas. A API retorna um task UID. Aguarde ou monitore o status antes de assumir que a mudança está disponível.
Adicionando documentos
const index = search.index('products');
const task = await index.addDocuments([
{
id: 'product-42',
tenantId: 'tenant-a',
name: 'Teclado Mecânico',
description: 'Teclado compacto com switches táteis',
category: 'peripherals',
priceCents: 45990,
inStock: true,
updatedAt: '2026-09-17T18:00:00Z'
}
]);
await search.waitForTask(task.taskUid);Meilisearch substitui documentos com a mesma primary key quando usa addDocuments. Envie campos completos ou entenda a diferença para updates parciais.
Indexação em lotes
Não envie um request por documento:
for (const batch of chunk(products, 1000)) {
const task = await index.addDocuments(batch);
await search.waitForTask(task.taskUid);
}O tamanho ideal depende do documento, rede e capacidade do servidor. Monitore duração e memória.
Primeira busca
const result = await index.search('tecldo mecanico', {
limit: 20
});
console.log(result.hits);A tolerância a typos ajuda a encontrar “teclado mecânico” mesmo com erros, mas precisa ser avaliada para IDs, códigos e termos curtos.
Searchable attributes
const task = await index.updateSearchableAttributes([
'name',
'description',
'category'
]);
await search.waitForTask(task.taskUid);A ordem influencia prioridade. Coloque campos mais importantes primeiro. Não indexe texto sem valor de busca.
Filterable attributes
const task = await index.updateFilterableAttributes([
'tenantId',
'category',
'inStock',
'priceCents'
]);Um campo só pode ser usado em filtros depois de configurado. Alterações de settings podem reindexar documentos.
Filtro por tenant
const result = await index.search(query, {
filter: [
`tenantId = "${safeTenantId}"`,
'inStock = true'
]
});Não monte filtros com texto arbitrário do usuário. Use valores validados e escape conforme o SDK. Mais importante: uma search key pública precisa restringir o tenant, pois esconder o filtro na interface não é autorização.
Tenant tokens
Meilisearch oferece mecanismos de tokens ou search rules para limitar filtros. Gere-os no backend e use expiração curta. Nunca deixe o cliente escolher um filtro de tenant sem restrição criptográfica.
Veja Multi-Tenancy no Node.js.
Sortable attributes
await index.updateSortableAttributes([
'priceCents',
'updatedAt'
]);Uso:
await index.search(query, {
sort: ['priceCents:asc']
});Ordenar apenas por preço pode reduzir relevância textual. Ofereça modos de ordenação explícitos.
Facetas
const result = await index.search(query, {
facets: ['category', 'inStock'],
limit: 20
});Facetas alimentam filtros e contagens. Campos de alta cardinalidade podem aumentar custo.
Ranking rules
Meilisearch usa regras como words, typo, proximity, attribute, sort e exactness. Personalizações precisam ser avaliadas com um conjunto de consultas reais.
await index.updateRankingRules([
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness'
]);Sinônimos
await index.updateSynonyms({
laptop: ['notebook'],
celular: ['smartphone']
});Sinônimos ajudam vocabulário de negócio, mas podem criar resultados excessivos. Versione e teste alterações.
Stop words
await index.updateStopWords([
'de', 'da', 'do', 'e', 'para'
]);Não remova palavras que fazem diferença no domínio. Em títulos curtos, stop words podem mudar a intenção.
Destacando resultados
const result = await index.search(query, {
attributesToHighlight: ['name', 'description'],
highlightPreTag: '<mark>',
highlightPostTag: '</mark>'
});Sanitize o HTML antes de renderizar. O conteúdo indexado pode ter vindo de usuário.
Recorte de texto
await index.search(query, {
attributesToCrop: ['description'],
cropLength: 20,
cropMarker: '…'
});Isso reduz payload e melhora a apresentação.
Paginação
Para interfaces simples:
await index.search(query, {
offset: 40,
limit: 20
});Para obter total e páginas, use o modo de paginação suportado pela versão do SDK. Grandes offsets aumentam custo; limite profundidade ou use navegação adequada ao produto.
Campos retornados
await index.search(query, {
attributesToRetrieve: [
'id', 'name', 'priceCents', 'inStock'
]
});Não retorne campos internos ou sensíveis apenas porque estão no documento.
Fonte de verdade
Após o usuário selecionar um resultado, busque o registro atualizado no banco. Estoque, preço e permissão podem ter mudado depois da indexação.
Sincronização com Outbox
- transação atualiza o produto;
- grava um evento na outbox;
- worker lê o evento;
- atualiza Meilisearch;
- marca o evento como concluído;
- retries reutilizam a mesma chave.
Consulte Outbox Pattern no Node.js.
Atualização idempotente
Use o ID do banco como primary key. Repetir a atualização produz o mesmo documento. Veja Idempotência em APIs Node.js.
Exclusão
const task = await index.deleteDocument('product-42');
await search.waitForTask(task.taskUid);Exclusões também são assíncronas. Para requisitos de privacidade, monitore conclusão e cópias de backup.
Reindexação sem downtime
Quando o schema muda:
- crie
products_v2; - aplique settings;
- carregue documentos;
- execute testes;
- troque o índice usado pela aplicação;
- remova a versão antiga depois.
Use aliases ou estratégia equivalente suportada pela plataforma.
Busca híbrida e vetorial
Versões atuais podem oferecer recursos de busca semântica. Avalie custo, modelo, armazenamento de embeddings e filtros. Para PostgreSQL com vetores, consulte pgvector no Node.js.
Timeout
const result = await Promise.race([
index.search(query, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Search timeout')), 1500)
)
]);Prefira AbortSignal quando o SDK suportar. Defina fallback para indisponibilidade.
Fallback
Uma API pode:
- retornar cache recente;
- usar busca limitada no PostgreSQL;
- mostrar mensagem de indisponibilidade;
- desativar sugestões enquanto mantém outras rotas.
Use Circuit Breaker quando falhas repetidas sobrecarregam o serviço. Veja Circuit Breaker no Node.js.
API keys
- Master key apenas em ambiente administrativo.
- Key de ingestão no worker.
- Search key no backend ou frontend.
- Tenant token para isolamento.
- Rotação e escopo mínimo.
Veja Gestão de Segredos no Node.js.
Observabilidade
Monitore:
- latência de busca p50, p95 e p99;
- zero-result rate;
- consultas mais frequentes;
- tasks falhas;
- lag da indexação;
- tamanho dos índices;
- tempo de reindexação;
- uso de memória e disco.
Qualidade
Crie um conjunto de consultas e resultados esperados. Meça:
- precision@k;
- MRR;
- taxa de clique;
- conversão;
- zero results;
- reformulações da consulta.
Ranking deve ser avaliado com comportamento do usuário, não apenas por exemplos manuais.
Testes
Use Meilisearch real em container. Teste settings, filtros de tenant, typos, facetas, tasks, reindexação, exclusão e falhas.
Consulte Testcontainers no Node.js.
Erros comuns
- Master key no navegador: comprometimento total.
- Sem filtro de tenant: vazamento de dados.
- Indexar um por vez: throughput baixo.
- Ignorar tasks: aplicação assume mudança inexistente.
- Índice como fonte de verdade: preço e estoque ficam incorretos.
- Ranking sem avaliação: resultados ruins.
- HTML destacado sem sanitização: XSS.
- Reindexar no índice ativo: busca fica inconsistente.
Conclusão
O Meilisearch no Node.js entrega busca instantânea com tolerância a typos, filtros, facetas e ranking configurável. O SDK JavaScript simplifica índices, documentos, settings e consultas.
Mantenha o banco como fonte de verdade, sincronize por eventos, use lotes e aguarde tasks. Proteja tenant e API keys, teste relevância e faça reindexações versionadas. Assim, a busca melhora a experiência sem assumir responsabilidades transacionais que pertencem ao banco principal.


