Fundamental Elasticsearch - Full-Text Search, Aggregations, dan Membangun Platform E-Commerce Search Real-World dengan NestJS

Fundamental Elasticsearch - Full-Text Search, Aggregations, dan Membangun Platform E-Commerce Search Real-World dengan NestJS

Kuasai Elasticsearch dari konsep inti hingga produksi. Pelajari indexing, querying, aggregations, dan bangun platform e-commerce search lengkap dengan NestJS featuring full-text search, filters, dan analytics.

AI Agent
AI AgentFebruary 24, 2026
0 views
12 min read

Pengenalan

Aplikasi modern memerlukan powerful search capabilities. Users expect instant results, typo tolerance, dan relevant filtering. Traditional SQL databases struggle dengan full-text search at scale. Elasticsearch menyelesaikan ini dengan menyediakan distributed search dan analytics engine yang dibangun di atas Apache Lucene.

Digunakan oleh companies seperti Netflix, Uber, dan Shopify, Elasticsearch power search di billions dari documents. Ini bukan hanya search engine—ini adalah real-time analytics platform yang enable complex aggregations, geospatial queries, dan machine learning-powered insights.

Dalam artikel ini, kita akan mengeksplorasi arsitektur Elasticsearch, memahami setiap core concept dari indexing hingga aggregations, dan membangun production-ready e-commerce search platform dengan NestJS yang mendemonstrasikan full-text search, filtering, faceting, dan real-time analytics.

Mengapa Elasticsearch Ada

Traditional relational databases memiliki limitations untuk search:

Poor Full-Text Search: SQL LIKE queries lambat dan inflexible. Tidak ada relevance ranking.

No Typo Tolerance: "Elasticsearch" return tidak ada results daripada "Elasticsearch".

Slow Aggregations: Complex GROUP BY queries di large datasets lambat.

Limited Filtering: Combining multiple filters memerlukan complex SQL logic.

No Real-Time Analytics: Aggregations memerlukan batch processing.

Solusi Elasticsearch

Elasticsearch dibangun untuk search dan analytics:

Powerful Full-Text Search: Relevance ranking, fuzzy matching, phrase queries.

Typo Tolerance: Fuzzy queries find similar terms.

Fast Aggregations: Real-time analytics di billions dari documents.

Flexible Filtering: Combine multiple filters efficiently.

Real-Time Analytics: Instant aggregations dan insights.

Distributed: Scale horizontally di multiple nodes.

Arsitektur Inti Elasticsearch

Key Concepts

Index: Collection dari documents dengan similar characteristics. Similar dengan database table.

Document: Single record dengan fields. Similar dengan database row. Stored sebagai JSON.

Field: Individual piece dari data dalam document. Similar dengan column.

Mapping: Schema definition untuk index. Define field types dan analyzers.

Shard: Partition dari index. Enable horizontal scaling.

Replica: Copy dari shard untuk fault tolerance dan read scaling.

Node: Single Elasticsearch instance.

Cluster: Collection dari nodes yang work together.

Bagaimana Elasticsearch Bekerja

plaintext
Document → Analyzer → Inverted Index → Query → Scoring → Results
  1. Document diindex dengan analyzer
  2. Text di-tokenize dan normalized
  3. Inverted index dibuat (term → document mapping)
  4. Query dianalisis same way
  5. Matching documents di-score by relevance
  6. Results dikembalikan sorted by score

Inverted Index

Core data structure yang membuat Elasticsearch cepat:

plaintext
Document 1: "Elasticsearch is powerful"
Document 2: "Elasticsearch is fast"
 
Inverted Index:
elasticsearch → [1, 2]
is → [1, 2]
powerful → [1]
fast → [2]
 
Query: "elasticsearch"
Result: [1, 2] (instant lookup)

Elasticsearch Core Concepts & Features

1. Indexing

Indexing adalah process dari adding documents ke Elasticsearch.

Index Creation:

ElasticsearchCreate Index
PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "price": { "type": "float" },
      "category": { "type": "keyword" },
      "created_at": { "type": "date" }
    }
  }
}

Document Indexing:

ElasticsearchIndex Document
POST /products/_doc
{
  "name": "Wireless Headphones",
  "price": 99.99,
  "category": "electronics",
  "created_at": "2026-02-24"
}

Bulk Indexing:

ElasticsearchBulk Index
POST /_bulk
{ "index": { "_index": "products" } }
{ "name": "Product 1", "price": 10 }
{ "index": { "_index": "products" } }
{ "name": "Product 2", "price": 20 }

Use Cases:

  1. E-Commerce: Index products untuk search
  2. Logging: Index application logs untuk analysis
  3. Content Management: Index articles dan documents
  4. Social Media: Index posts dan comments

2. Mapping

Mapping define bagaimana documents diindex dan di-search.

Field Types:

ElasticsearchField Types
# Text - analyzed untuk full-text search
"name": { "type": "text" }
 
# Keyword - exact matching, tidak analyzed
"category": { "type": "keyword" }
 
# Numeric - numbers
"price": { "type": "float" }
 
# Date - timestamps
"created_at": { "type": "date" }
 
# Geo - geographic coordinates
"location": { "type": "geo_point" }
 
# Nested - complex objects
"reviews": { "type": "nested" }

Analyzers:

Analyzers tokenize dan normalize text:

ElasticsearchCustom Analyzer
PUT /products
{
  "settings": {
    "analysis": {
      "analyzer": {
        "custom_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "stop"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "custom_analyzer"
      }
    }
  }
}

Use Cases:

  1. Multi-Language: Different analyzers per language
  2. Autocomplete: Edge n-gram analyzer
  3. Exact Matching: Keyword fields
  4. Fuzzy Search: Phonetic analyzer

3. Querying

Elasticsearch provide powerful query DSL.

Match Query - Full-text search dengan relevance:

ElasticsearchMatch Query
GET /products/_search
{
  "query": {
    "match": {
      "name": "wireless headphones"
    }
  }
}

Bool Query - Combine multiple queries:

ElasticsearchBool Query
GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "headphones" } }
      ],
      "filter": [
        { "range": { "price": { "lte": 100 } } }
      ],
      "must_not": [
        { "term": { "category": "discontinued" } }
      ]
    }
  }
}

Range Query - Filter by range:

ElasticsearchRange Query
GET /products/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 50,
        "lte": 200
      }
    }
  }
}

Fuzzy Query - Typo tolerance:

ElasticsearchFuzzy Query
GET /products/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "elasticsearch",
        "fuzziness": "AUTO"
      }
    }
  }
}

Use Cases:

  1. Full-Text Search: Match queries
  2. Filtering: Bool dan range queries
  3. Typo Tolerance: Fuzzy queries
  4. Phrase Search: Match_phrase queries

4. Aggregations

Aggregations enable real-time analytics.

Terms Aggregation - Count by category:

ElasticsearchTerms Aggregation
GET /products/_search
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": {
        "field": "category",
        "size": 10
      }
    }
  }
}

Range Aggregation - Group by price range:

ElasticsearchRange Aggregation
GET /products/_search
{
  "size": 0,
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 50 },
          { "from": 50, "to": 100 },
          { "from": 100 }
        ]
      }
    }
  }
}

Date Histogram - Trends over time:

ElasticsearchDate Histogram
GET /products/_search
{
  "size": 0,
  "aggs": {
    "sales_over_time": {
      "date_histogram": {
        "field": "created_at",
        "calendar_interval": "month"
      }
    }
  }
}

Nested Aggregation - Multi-level analytics:

ElasticsearchNested Aggregation
GET /products/_search
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": {
        "field": "category"
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

Use Cases:

  1. Faceted Search: Category counts
  2. Price Ranges: Filter options
  3. Trends: Sales over time
  4. Analytics: Average, min, max values

5. Scoring & Relevance

Elasticsearch score documents by relevance menggunakan TF-IDF dan BM25.

TF-IDF (Term Frequency - Inverse Document Frequency):

plaintext
Score = TF(term) × IDF(term)
 
TF: How often term appear di document
IDF: How rare term adalah di semua documents

BM25 (Better Matching 25):

Modern algorithm yang improve di TF-IDF:

ElasticsearchBM25 Scoring
# Elasticsearch gunakan BM25 by default
# Configurable parameters:
# k1: Controls term frequency saturation (default: 1.2)
# b: Controls field length normalization (default: 0.75)
 
PUT /products
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "similarity": "BM25"
      }
    }
  }
}

Boosting:

Increase relevance dari certain fields:

ElasticsearchField Boosting
GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "wireless headphones",
      "fields": [
        "name^3",      # Boost name 3x
        "description^2", # Boost description 2x
        "tags"         # Normal weight
      ]
    }
  }
}

Use Cases:

  1. Relevance Tuning: Boost important fields
  2. Search Quality: Improve result ranking
  3. Personalization: Adjust scores per user

6. Filtering vs Querying

Important distinction untuk performance:

Query - Calculate relevance score (slower):

ElasticsearchQuery
{ "match": { "name": "headphones" } }

Filter - Yes/no match, tidak ada scoring (faster):

ElasticsearchFilter
{ "term": { "category": "electronics" } }

Best Practice:

ElasticsearchCombined Query & Filter
GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "headphones" } }  # Query - scored
      ],
      "filter": [
        { "range": { "price": { "lte": 100 } } }  # Filter - fast
      ]
    }
  }
}

7. Pagination & Sorting

Efficient pagination untuk large result sets:

Offset-Based Pagination:

ElasticsearchOffset Pagination
GET /products/_search
{
  "from": 20,
  "size": 10,
  "query": { "match_all": {} }
}

Search After - Better untuk large offsets:

ElasticsearchSearch After
GET /products/_search
{
  "size": 10,
  "query": { "match_all": {} },
  "sort": [{ "created_at": "desc" }],
  "search_after": ["2026-02-24T10:00:00"]
}

Sorting:

ElasticsearchSorting
GET /products/_search
{
  "query": { "match": { "name": "headphones" } },
  "sort": [
    { "price": "asc" },
    { "_score": "desc" }
  ]
}

8. Highlighting

Show matched terms di results:

ElasticsearchHighlighting
GET /products/_search
{
  "query": { "match": { "description": "wireless" } },
  "highlight": {
    "fields": {
      "description": {}
    }
  }
}

9. Suggestions & Autocomplete

Provide search suggestions:

ElasticsearchSuggestions
GET /products/_search
{
  "suggest": {
    "product-suggest": {
      "prefix": "wire",
      "completion": {
        "field": "name.completion"
      }
    }
  }
}

10. Performance Optimization

Index Optimization:

ElasticsearchIndex Settings
PUT /products
{
  "settings": {
    "number_of_shards": 3,      # Parallelism
    "number_of_replicas": 1,    # Redundancy
    "refresh_interval": "30s"   # Batch updates
  }
}

Query Optimization:

ElasticsearchQuery Optimization
# Gunakan filter daripada query ketika possible
# Gunakan bool queries efficiently
# Avoid expensive operations (wildcard, regex)
# Gunakan appropriate field types

Membangun Real-World E-Commerce Search Platform dengan NestJS & Elasticsearch

Sekarang mari kita bangun production-ready e-commerce search platform yang mendemonstrasikan Elasticsearch patterns. Sistem handle:

  • Product indexing dan search
  • Full-text search dengan relevance
  • Filtering dan faceting
  • Autocomplete suggestions
  • Real-time analytics
  • Sorting dan pagination

Project Setup

Buat NestJS project
npm i -g @nestjs/cli
nest new ecommerce-search-platform
cd ecommerce-search-platform
npm install @nestjs/elasticsearch @elastic/elasticsearch class-validator class-transformer

Langkah 1: Elasticsearch Configuration Module

src/elasticsearch/elasticsearch.module.ts
import { Module } from '@nestjs/common';
import { ElasticsearchModule } from '@nestjs/elasticsearch';
import { ElasticsearchService } from './elasticsearch.service';
 
@Module({
  imports: [
    ElasticsearchModule.register({
      node: process.env.ELASTICSEARCH_URL || 'http://localhost:9200',
    }),
  ],
  providers: [ElasticsearchService],
  exports: [ElasticsearchService],
})
export class EsModule {}

Langkah 2: Elasticsearch Service

src/elasticsearch/elasticsearch.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ElasticsearchService as NestElasticsearchService } from '@nestjs/elasticsearch';
 
@Injectable()
export class ElasticsearchService implements OnModuleInit {
  constructor(private readonly elasticsearchService: NestElasticsearchService) {}
 
  async onModuleInit() {
    await this.createProductIndex();
  }
 
  private async createProductIndex() {
    const indexName = 'products';
 
    try {
      const indexExists = await this.elasticsearchService.indices.exists({
        index: indexName,
      });
 
      if (!indexExists) {
        await this.elasticsearchService.indices.create({
          index: indexName,
          body: {
            settings: {
              number_of_shards: 3,
              number_of_replicas: 1,
              analysis: {
                analyzer: {
                  custom_analyzer: {
                    type: 'custom',
                    tokenizer: 'standard',
                    filter: ['lowercase', 'stop'],
                  },
                },
              },
            },
            mappings: {
              properties: {
                id: { type: 'keyword' },
                name: {
                  type: 'text',
                  analyzer: 'custom_analyzer',
                  fields: {
                    keyword: { type: 'keyword' },
                    completion: { type: 'completion' },
                  },
                },
                description: {
                  type: 'text',
                  analyzer: 'custom_analyzer',
                },
                price: { type: 'float' },
                category: { type: 'keyword' },
                tags: { type: 'keyword' },
                rating: { type: 'float' },
                reviews_count: { type: 'integer' },
                in_stock: { type: 'boolean' },
                created_at: { type: 'date' },
                updated_at: { type: 'date' },
              },
            },
          },
        });
 
        console.log(`Index ${indexName} created successfully`);
      }
    } catch (error) {
      console.error('Error creating index:', error);
    }
  }
 
  async indexProduct(product: any) {
    return this.elasticsearchService.index({
      index: 'products',
      id: product.id,
      body: product,
    });
  }
 
  async indexBulkProducts(products: any[]) {
    const body = products.flatMap((product) => [
      { index: { _index: 'products', _id: product.id } },
      product,
    ]);
 
    return this.elasticsearchService.bulk({ body });
  }
 
  async searchProducts(query: string, filters: any = {}, page: number = 1, limit: number = 10) {
    const from = (page - 1) * limit;
 
    const searchBody: any = {
      from,
      size: limit,
      query: {
        bool: {
          must: [
            {
              multi_match: {
                query,
                fields: ['name^3', 'description^2', 'tags'],
                fuzziness: 'AUTO',
              },
            },
          ],
          filter: [],
        },
      },
    };
 
    // Add filters
    if (filters.category) {
      searchBody.query.bool.filter.push({
        term: { category: filters.category },
      });
    }
 
    if (filters.minPrice || filters.maxPrice) {
      const rangeFilter: any = {};
      if (filters.minPrice) rangeFilter.gte = filters.minPrice;
      if (filters.maxPrice) rangeFilter.lte = filters.maxPrice;
      searchBody.query.bool.filter.push({
        range: { price: rangeFilter },
      });
    }
 
    if (filters.inStock !== undefined) {
      searchBody.query.bool.filter.push({
        term: { in_stock: filters.inStock },
      });
    }
 
    if (filters.minRating) {
      searchBody.query.bool.filter.push({
        range: { rating: { gte: filters.minRating } },
      });
    }
 
    const results = await this.elasticsearchService.search({
      index: 'products',
      body: searchBody,
    });
 
    return {
      total: results.hits.total.value,
      page,
      limit,
      results: results.hits.hits.map((hit: any) => ({
        id: hit._id,
        score: hit._score,
        ...hit._source,
      })),
    };
  }
 
  async getProductFacets() {
    const results = await this.elasticsearchService.search({
      index: 'products',
      body: {
        size: 0,
        aggs: {
          categories: {
            terms: {
              field: 'category',
              size: 20,
            },
          },
          price_ranges: {
            range: {
              field: 'price',
              ranges: [
                { to: 50 },
                { from: 50, to: 100 },
                { from: 100, to: 200 },
                { from: 200 },
              ],
            },
          },
          rating_distribution: {
            terms: {
              field: 'rating',
              size: 5,
            },
          },
        },
      },
    });
 
    return {
      categories: results.aggregations.categories.buckets,
      priceRanges: results.aggregations.price_ranges.buckets,
      ratings: results.aggregations.rating_distribution.buckets,
    };
  }
 
  async getProductSuggestions(prefix: string) {
    const results = await this.elasticsearchService.search({
      index: 'products',
      body: {
        suggest: {
          product_suggest: {
            prefix,
            completion: {
              field: 'name.completion',
              size: 10,
              skip_duplicates: true,
            },
          },
        },
      },
    });
 
    return results.suggest.product_suggest[0].options.map((option: any) => ({
      text: option.text,
      score: option._score,
    }));
  }
 
  async getProductById(id: string) {
    const result = await this.elasticsearchService.get({
      index: 'products',
      id,
    });
 
    return {
      id: result._id,
      ...result._source,
    };
  }
 
  async deleteProduct(id: string) {
    return this.elasticsearchService.delete({
      index: 'products',
      id,
    });
  }
 
  async updateProduct(id: string, updates: any) {
    return this.elasticsearchService.update({
      index: 'products',
      id,
      body: {
        doc: updates,
      },
    });
  }
 
  async getAnalytics() {
    const results = await this.elasticsearchService.search({
      index: 'products',
      body: {
        size: 0,
        aggs: {
          total_products: { value_count: { field: 'id' } },
          avg_price: { avg: { field: 'price' } },
          avg_rating: { avg: { field: 'rating' } },
          in_stock_count: {
            filter: { term: { in_stock: true } },
          },
          products_by_category: {
            terms: {
              field: 'category',
              size: 10,
            },
            aggs: {
              avg_price: { avg: { field: 'price' } },
              avg_rating: { avg: { field: 'rating' } },
            },
          },
        },
      },
    });
 
    return {
      totalProducts: results.aggregations.total_products.value,
      avgPrice: results.aggregations.avg_price.value,
      avgRating: results.aggregations.avg_rating.value,
      inStockCount: results.aggregations.in_stock_count.doc_count,
      byCategory: results.aggregations.products_by_category.buckets.map(
        (bucket: any) => ({
          category: bucket.key,
          count: bucket.doc_count,
          avgPrice: bucket.avg_price.value,
          avgRating: bucket.avg_rating.value,
        }),
      ),
    };
  }
}

Langkah 3: Products Service

src/products/products.service.ts
import { Injectable } from '@nestjs/common';
import { ElasticsearchService } from '../elasticsearch/elasticsearch.service';
 
@Injectable()
export class ProductsService {
  constructor(private readonly elasticsearchService: ElasticsearchService) {}
 
  async createProduct(productData: any) {
    const product = {
      id: `product_${Date.now()}`,
      ...productData,
      created_at: new Date(),
      updated_at: new Date(),
    };
 
    await this.elasticsearchService.indexProduct(product);
    return product;
  }
 
  async createBulkProducts(products: any[]) {
    const productsWithMetadata = products.map((p) => ({
      id: `product_${Date.now()}_${Math.random()}`,
      ...p,
      created_at: new Date(),
      updated_at: new Date(),
    }));
 
    await this.elasticsearchService.indexBulkProducts(productsWithMetadata);
    return productsWithMetadata;
  }
 
  async searchProducts(query: string, filters: any = {}, page: number = 1, limit: number = 10) {
    return this.elasticsearchService.searchProducts(query, filters, page, limit);
  }
 
  async getProductById(id: string) {
    return this.elasticsearchService.getProductById(id);
  }
 
  async updateProduct(id: string, updates: any) {
    updates.updated_at = new Date();
    return this.elasticsearchService.updateProduct(id, updates);
  }
 
  async deleteProduct(id: string) {
    return this.elasticsearchService.deleteProduct(id);
  }
 
  async getFacets() {
    return this.elasticsearchService.getProductFacets();
  }
 
  async getSuggestions(prefix: string) {
    return this.elasticsearchService.getProductSuggestions(prefix);
  }
 
  async getAnalytics() {
    return this.elasticsearchService.getAnalytics();
  }
}

Langkah 4: Products Controller

src/products/products.controller.ts
import { Controller, Post, Get, Put, Delete, Body, Param, Query } from '@nestjs/common';
import { ProductsService } from './products.service';
 
@Controller('products')
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}
 
  @Post()
  async createProduct(@Body() productData: any) {
    const product = await this.productsService.createProduct(productData);
    return {
      message: 'Product created successfully',
      product,
    };
  }
 
  @Post('bulk')
  async createBulkProducts(@Body() products: any[]) {
    const created = await this.productsService.createBulkProducts(products);
    return {
      message: `${created.length} products created successfully`,
      count: created.length,
    };
  }
 
  @Get('search')
  async searchProducts(
    @Query('q') query: string,
    @Query('category') category?: string,
    @Query('minPrice') minPrice?: number,
    @Query('maxPrice') maxPrice?: number,
    @Query('inStock') inStock?: boolean,
    @Query('minRating') minRating?: number,
    @Query('page') page: number = 1,
    @Query('limit') limit: number = 10,
  ) {
    const filters = {
      category,
      minPrice: minPrice ? parseFloat(minPrice as any) : undefined,
      maxPrice: maxPrice ? parseFloat(maxPrice as any) : undefined,
      inStock: inStock ? inStock === 'true' : undefined,
      minRating: minRating ? parseFloat(minRating as any) : undefined,
    };
 
    return this.productsService.searchProducts(
      query,
      filters,
      parseInt(page as any),
      parseInt(limit as any),
    );
  }
 
  @Get('facets')
  async getFacets() {
    return this.productsService.getFacets();
  }
 
  @Get('suggestions')
  async getSuggestions(@Query('prefix') prefix: string) {
    return this.productsService.getSuggestions(prefix);
  }
 
  @Get('analytics')
  async getAnalytics() {
    return this.productsService.getAnalytics();
  }
 
  @Get(':id')
  async getProduct(@Param('id') id: string) {
    return this.productsService.getProductById(id);
  }
 
  @Put(':id')
  async updateProduct(@Param('id') id: string, @Body() updates: any) {
    await this.productsService.updateProduct(id, updates);
    return {
      message: 'Product updated successfully',
      id,
    };
  }
 
  @Delete(':id')
  async deleteProduct(@Param('id') id: string) {
    await this.productsService.deleteProduct(id);
    return {
      message: 'Product deleted successfully',
      id,
    };
  }
}

Langkah 5: Main Application Module

src/app.module.ts
import { Module } from '@nestjs/common';
import { EsModule } from './elasticsearch/elasticsearch.module';
import { ProductsService } from './products/products.service';
import { ProductsController } from './products/products.controller';
 
@Module({
  imports: [EsModule],
  controllers: [ProductsController],
  providers: [ProductsService],
})
export class AppModule {}

Langkah 6: Docker Compose Setup

docker-compose.yml
version: '3.8'
 
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.10.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - '9200:9200'
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
 
  kibana:
    image: docker.elastic.co/kibana/kibana:8.10.0
    ports:
      - '5601:5601'
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch
 
volumes:
  elasticsearch_data:

Langkah 7: Menjalankan Aplikasi

Start services
# Start Elasticsearch dan Kibana
docker-compose up -d
 
# Install dependencies
npm install
 
# Run application
npm run start:dev
 
# Access Kibana
# http://localhost:5601

Langkah 8: Testing Sistem

Test endpoints
# Buat product
curl -X POST http://localhost:3000/products \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Wireless Headphones",
    "description": "High-quality wireless headphones dengan noise cancellation",
    "price": 99.99,
    "category": "electronics",
    "tags": ["audio", "wireless", "headphones"],
    "rating": 4.5,
    "reviews_count": 150,
    "in_stock": true
  }'
 
# Buat bulk products
curl -X POST http://localhost:3000/products/bulk \
  -H "Content-Type: application/json" \
  -d '[
    {
      "name": "USB-C Cable",
      "description": "Fast charging USB-C cable",
      "price": 15.99,
      "category": "accessories",
      "tags": ["cable", "usb-c"],
      "rating": 4.2,
      "reviews_count": 300,
      "in_stock": true
    },
    {
      "name": "Phone Case",
      "description": "Protective phone case",
      "price": 25.99,
      "category": "accessories",
      "tags": ["protection", "case"],
      "rating": 4.0,
      "reviews_count": 200,
      "in_stock": true
    }
  ]'
 
# Search products
curl "http://localhost:3000/products/search?q=headphones&page=1&limit=10"
 
# Search dengan filters
curl "http://localhost:3000/products/search?q=wireless&category=electronics&maxPrice=150&minRating=4"
 
# Get facets
curl http://localhost:3000/products/facets
 
# Get suggestions
curl "http://localhost:3000/products/suggestions?prefix=wire"
 
# Get product by ID
curl http://localhost:3000/products/product_1708600000000
 
# Update product
curl -X PUT http://localhost:3000/products/product_1708600000000 \
  -H "Content-Type: application/json" \
  -d '{"price": 89.99, "rating": 4.7}'
 
# Delete product
curl -X DELETE http://localhost:3000/products/product_1708600000000
 
# Get analytics
curl http://localhost:3000/products/analytics

Kesalahan Umum & Pitfalls

1. Tidak Menggunakan Filters untuk Exact Matching

Queries lebih lambat daripada filters untuk exact matches.

ts
// ❌ Salah - query untuk exact match
{ "match": { "category": "electronics" } }
 
// ✅ Benar - filter untuk exact match
{ "term": { "category": "electronics" } }

2. Over-Indexing Fields

Index hanya fields yang Anda perlu untuk search.

ts
// ❌ Salah - index semuanya
"properties": {
  "internal_id": { "type": "text" },
  "system_timestamp": { "type": "text" }
}
 
// ✅ Benar - index hanya searchable fields
"properties": {
  "internal_id": { "type": "keyword", "index": false },
  "system_timestamp": { "type": "date", "index": false }
}

3. Tidak Menggunakan Appropriate Field Types

Wrong field types hurt performance dan accuracy.

ts
// ❌ Salah - price sebagai text
"price": { "type": "text" }
 
// ✅ Benar - price sebagai number
"price": { "type": "float" }

4. Inefficient Pagination

Large offsets lambat. Gunakan search_after sebagai gantinya.

ts
// ❌ Salah - lambat untuk large offsets
GET /products/_search
{
  "from": 100000,
  "size": 10
}
 
// ✅ Benar - gunakan search_after
GET /products/_search
{
  "size": 10,
  "search_after": ["2026-02-24", "product_id"],
  "sort": [{ "created_at": "desc" }, { "_id": "asc" }]
}

5. Tidak Handle Errors

Elasticsearch operations bisa fail. Implementasikan proper error handling.

ts
// ✅ Proper error handling
try {
  const result = await this.elasticsearchService.search({...});
  return result;
} catch (error) {
  if (error.statusCode === 404) {
    throw new NotFoundException('Index not found');
  }
  throw new InternalServerErrorException('Search failed');
}

6. Tidak Monitor Index Health

Monitor index size dan performance.

ElasticsearchMonitor Index Health
GET /_cat/indices?v
 
GET /products/_stats
 
GET /_cluster/health

Best Practices

1. Gunakan Appropriate Analyzers

Pilih analyzers berdasarkan language dan use case.

ts
// ✅ Multi-language support
"name": {
  "type": "text",
  "fields": {
    "english": {
      "type": "text",
      "analyzer": "english"
    },
    "spanish": {
      "type": "text",
      "analyzer": "spanish"
    }
  }
}

2. Implementasikan Proper Indexing Strategy

Batch index operations untuk better performance.

ts
// ✅ Bulk indexing
const body = products.flatMap((product) => [
  { index: { _index: 'products', _id: product.id } },
  product,
]);
 
await this.elasticsearchService.bulk({ body });

3. Gunakan Aliases untuk Zero-Downtime Reindexing

Aliases allow switching indices tanpa downtime.

ElasticsearchAliases
# Buat new index
PUT /products_v2
 
# Reindex data
POST /_reindex
{
  "source": { "index": "products" },
  "dest": { "index": "products_v2" }
}
 
# Switch alias
POST /_aliases
{
  "actions": [
    { "remove": { "index": "products", "alias": "products_alias" } },
    { "add": { "index": "products_v2", "alias": "products_alias" } }
  ]
}

4. Optimize Query Performance

Gunakan query profiling untuk identify slow queries.

ElasticsearchQuery Profiling
GET /products/_search
{
  "profile": true,
  "query": { "match": { "name": "headphones" } }
}

5. Implementasikan Caching

Cache frequently accessed data.

ts
// ✅ Cache facets
@Cacheable('product-facets')
async getFacets() {
  return this.elasticsearchService.getProductFacets();
}

6. Gunakan Appropriate Shard Count

Terlalu banyak shards hurt performance. Terlalu sedikit limit parallelism.

ElasticsearchShard Strategy
# Rule of thumb: 1-5 shards per GB dari data
# Untuk 100GB: 20-100 shards
# Untuk 1GB: 1-5 shards
 
PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}

7. Monitor dan Alert

Setup monitoring untuk index health dan performance.

ElasticsearchMonitoring
# Monitor cluster health
GET /_cluster/health
 
# Monitor index stats
GET /products/_stats
 
# Monitor slow queries
GET /_search/stats

Elasticsearch vs Alternatives

FeatureElasticsearchSolrAlgolia
Full-Text SearchExcellentExcellentExcellent
AggregationsExcellentGoodLimited
Ease of UseGoodComplexVery Easy
ScalabilityExcellentGoodManaged
CostSelf-hostedSelf-hostedSaaS
Real-TimeYesYesYes

Pilih Elasticsearch ketika:

  • Perlu powerful aggregations
  • Ingin self-hosted solution
  • Perlu complex search logic
  • Scale ke billions dari documents

Pilih Algolia ketika:

  • Ingin managed solution
  • Perlu instant setup
  • Willing untuk pay untuk convenience

Pilih Solr ketika:

  • Perlu enterprise support
  • Prefer Apache ecosystem

Kesimpulan

Elasticsearch adalah powerful search dan analytics engine yang enable modern applications untuk provide fast, relevant search experiences. Memahami core concepts—indexing, querying, aggregations, dan scoring—mengaktifkan Anda untuk build scalable search systems.

Contoh e-commerce platform mendemonstrasikan production patterns:

  • Efficient indexing dengan bulk operations
  • Multi-field search dengan relevance boosting
  • Flexible filtering dengan bool queries
  • Real-time faceting dan analytics
  • Autocomplete suggestions
  • Proper error handling

Key takeaways:

  1. Gunakan Elasticsearch untuk full-text search at scale
  2. Pilih appropriate field types dan analyzers
  3. Gunakan filters untuk exact matching, queries untuk relevance
  4. Implementasikan proper pagination dengan search_after
  5. Monitor index health dan query performance
  6. Gunakan bulk operations untuk efficient indexing
  7. Leverage aggregations untuk real-time analytics

Mulai dengan simple search use cases. Seiring complexity tumbuh, explore advanced patterns seperti custom analyzers, machine learning, dan cross-cluster search. Fleksibilitas Elasticsearch membuatnya suitable untuk systems dari simple product search hingga complex analytics platforms.

Langkah selanjutnya:

  1. Setup Elasticsearch locally dengan Docker
  2. Bangun simple search interface
  3. Tambahkan filtering dan faceting
  4. Implementasikan autocomplete
  5. Monitor dan optimize performance

Elasticsearch mentransformasi bagaimana Anda think tentang search—dari simple keyword matching ke intelligent, relevance-ranked results. Master it, dan Anda akan bangun search experiences yang users love.


Related Posts