Build powerful search with Elasticsearch — index mappings, query DSL, bool queries, aggregations, highlighting, fuzzy search, and performance optimization.
Elasticsearch Core Concepts
- Index: Collection of documents (like a table)
- Document: JSON object (like a row)
- Mapping: Schema definition
- Shard: Horizontal partition for scaling
- Replica: Copy of shard for HA
Index Mapping
PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"product_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "stop", "snowball"]
}
}
}
},
"mappings": {
"properties": {
"id": { "type": "keyword" },
"name": {
"type": "text",
"analyzer": "product_analyzer",
"fields": {
"keyword": { "type": "keyword" },
"suggest": { "type": "completion" }
}
},
"description": { "type": "text", "analyzer": "product_analyzer" },
"price": { "type": "double" },
"category": { "type": "keyword" },
"tags": { "type": "keyword" },
"rating": { "type": "float" },
"in_stock": { "type": "boolean" },
"created_at": { "type": "date" }
}
}
}
Search Queries
const { Client } = require('@elastic/elasticsearch')
const client = new Client({ node: 'http://localhost:9200' })
// Full-text search with filters
const result = await client.search({
index: 'products',
body: {
query: {
bool: {
must: [
{
multi_match: {
query: 'wireless headphones',
fields: ['name^3', 'description'], // name weighted 3x
type: 'best_fields',
fuzziness: 'AUTO',
},
},
],
filter: [
{ term: { in_stock: true } },
{ range: { price: { gte: 20, lte: 200 } } },
{ terms: { category: ['electronics', 'audio'] } },
],
should: [
{ range: { rating: { gte: 4.0, boost: 2 } } },
],
},
},
sort: [
{ _score: 'desc' },
{ rating: 'desc' },
],
highlight: {
fields: {
name: { pre_tags: ['<mark>'], post_tags: ['</mark>'] },
description: { fragment_size: 150, number_of_fragments: 2 },
},
},
aggs: {
categories: { terms: { field: 'category', size: 10 } },
price_ranges: {
range: {
field: 'price',
ranges: [
{ to: 25 }, { from: 25, to: 50 },
{ from: 50, to: 100 }, { from: 100 },
],
},
},
avg_rating: { avg: { field: 'rating' } },
},
from: 0,
size: 20,
},
})
Autocomplete with Completion Suggester
// Index a document with suggest field
await client.index({
index: 'products',
body: {
name: 'Wireless Headphones',
'name.suggest': {
input: ['Wireless Headphones', 'Headphones Wireless', 'BT Headphones'],
weight: 10,
},
},
})
// Query autocomplete
const suggestions = await client.search({
index: 'products',
body: {
suggest: {
product_suggest: {
prefix: 'wire',
completion: {
field: 'name.suggest',
size: 5,
fuzzy: { fuzziness: 1 },
},
},
},
},
})
Aggregations for Faceted Search
// Get facets for search results
const facets = await client.search({
index: 'products',
body: {
size: 0, // Don't need documents, just aggregations
aggs: {
categories: { terms: { field: 'category', size: 20 } },
brands: { terms: { field: 'brand', size: 20 } },
price_stats: { stats: { field: 'price' } },
rating_histogram: {
histogram: { field: 'rating', interval: 0.5 },
},
},
},
})
Index Templates and Aliases
// Template for time-series indices
PUT /_index_template/logs
{
"index_patterns": ["logs-*"],
"template": {
"settings": { "number_of_shards": 1 },
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"message": { "type": "text" },
"service": { "type": "keyword" }
}
}
}
}
// Write to alias, switch indices without downtime
POST /_aliases
{
"actions": [
{ "add": { "index": "products-v2", "alias": "products" } },
{ "remove": { "index": "products-v1", "alias": "products" } }
]
}
Performance Tips
- Set
"doc_values": false on text fields not used for aggregations
- Use
filter context over query for non-scoring checks
- Use
keyword type for IDs, categories (exact match)
- Set
refresh_interval: -1 during bulk indexing, then restore
- Use bulk API for indexing (not individual requests)