Elasticsearch: Full-Text Search and Analytics
Index Mapping Design
import { Client } from '@elastic/elasticsearch';
const client = new Client({ node: 'http://localhost:9200' });
// Create index with explicit mapping
await client.indices.create({
index: 'products',
body: {
settings: {
number_of_shards: 3,
number_of_replicas: 1,
analysis: {
analyzer: {
product_analyzer: {
type: 'custom',
tokenizer: 'standard',
filter: ['lowercase', 'stop', 'stemmer'],
},
},
},
},
mappings: {
properties: {
id: { type: 'keyword' },
name: {
type: 'text',
analyzer: 'product_analyzer',
fields: {
keyword: { type: 'keyword' }, // For sorting/aggregation
suggest: { type: 'completion' }, // For autocomplete
},
},
description: { type: 'text', analyzer: 'product_analyzer' },
price: { type: 'double' },
category: { type: 'keyword' },
tags: { type: 'keyword' },
brand: { type: 'keyword' },
inStock: { type: 'boolean' },
rating: { type: 'float' },
createdAt: { type: 'date' },
},
},
},
});
Full-Text Search
// Basic multi-match search
const results = await client.search({
index: 'products',
body: {
query: {
multi_match: {
query: 'wireless noise cancelling headphones',
fields: ['name^3', 'description', 'tags^2'], // Boost name and tags
type: 'best_fields',
fuzziness: 'AUTO', // Handle typos
},
},
highlight: {
fields: { name: {}, description: { fragment_size: 150 } },
},
size: 20,
from: 0,
},
});
// Boolean query with filters
const filtered = await client.search({
index: 'products',
body: {
query: {
bool: {
must: [
{ multi_match: { query: 'headphones', fields: ['name', 'description'] } },
],
filter: [
{ term: { inStock: true } },
{ terms: { category: ['electronics', 'audio'] } },
{ range: { price: { gte: 50, lte: 500 } } },
{ range: { rating: { gte: 4.0 } } },
],
should: [
{ term: { brand: 'Sony' } }, // Boost Sony products
],
minimum_should_match: 0,
},
},
sort: [
{ _score: { order: 'desc' } },
{ rating: { order: 'desc' } },
],
},
});
Aggregations for Faceted Search
const facets = await client.search({
index: 'products',
body: {
query: { match: { name: 'headphones' } },
aggs: {
categories: {
terms: { field: 'category', size: 10 },
},
brands: {
terms: { field: 'brand', size: 10 },
},
price_ranges: {
range: {
field: 'price',
ranges: [
{ key: 'under_50', to: 50 },
{ key: '50_to_200', from: 50, to: 200 },
{ key: '200_to_500', from: 200, to: 500 },
{ key: 'over_500', from: 500 },
],
},
},
avg_rating: {
avg: { field: 'rating' },
},
stats: {
stats: { field: 'price' },
},
},
size: 20,
},
});
Autocomplete with Completion Suggester
// Index with completion field
const suggest = await client.search({
index: 'products',
body: {
suggest: {
product_suggest: {
prefix: 'wireles',
completion: {
field: 'name.suggest',
size: 5,
fuzzy: { fuzziness: 1 },
},
},
},
},
});
const suggestions = suggest.suggest.product_suggest[0].options
.map(o => ({ text: o.text, id: o._source.id }));
Index Management
# Check cluster health
curl http://localhost:9200/_cluster/health?pretty
# Index stats
curl http://localhost:9200/products/_stats?pretty
# Force merge for read-heavy index
curl -X POST http://localhost:9200/products/_forcemerge?max_num_segments=1
# Index lifecycle management (ILM)
# Rollover large indices automatically
Elasticsearch excels at full-text search; use PostgreSQL with tsvector for simpler use cases.