Master Elasticsearch from core concepts to production. Learn indexing, querying, aggregations, and build a complete e-commerce search platform with NestJS featuring full-text search, filters, and analytics.

Modern applications need powerful search capabilities. Users expect instant results, typo tolerance, and relevant filtering. Traditional SQL databases struggle with full-text search at scale. Elasticsearch solves this by providing a distributed search and analytics engine built on top of Apache Lucene.
Used by companies like Netflix, Uber, and Shopify, Elasticsearch powers search across billions of documents. It's not just a search engine—it's a real-time analytics platform that enables complex aggregations, geospatial queries, and machine learning-powered insights.
In this article, we'll explore Elasticsearch's architecture, understand every core concept from indexing to aggregations, and build a production-ready e-commerce search platform with NestJS that demonstrates full-text search, filtering, faceting, and real-time analytics.
Traditional relational databases have limitations for search:
Poor Full-Text Search: SQL LIKE queries are slow and inflexible. No relevance ranking.
No Typo Tolerance: "Elasticsearch" returns no results instead of "Elasticsearch".
Slow Aggregations: Complex GROUP BY queries on large datasets are slow.
Limited Filtering: Combining multiple filters requires complex SQL logic.
No Real-Time Analytics: Aggregations require batch processing.
Elasticsearch was built for search and analytics:
Powerful Full-Text Search: Relevance ranking, fuzzy matching, phrase queries.
Typo Tolerance: Fuzzy queries find similar terms.
Fast Aggregations: Real-time analytics on billions of documents.
Flexible Filtering: Combine multiple filters efficiently.
Real-Time Analytics: Instant aggregations and insights.
Distributed: Scales horizontally across multiple nodes.
Index: Collection of documents with similar characteristics. Similar to a database table.
Document: Single record with fields. Similar to a database row. Stored as JSON.
Field: Individual piece of data within a document. Similar to a column.
Mapping: Schema definition for an index. Defines field types and analyzers.
Shard: Partition of an index. Enables horizontal scaling.
Replica: Copy of a shard for fault tolerance and read scaling.
Node: Single Elasticsearch instance.
Cluster: Collection of nodes working together.
Document → Analyzer → Inverted Index → Query → Scoring → ResultsThe core data structure that makes Elasticsearch fast:
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)Indexing is the process of adding documents to Elasticsearch.
Index Creation:
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:
POST /products/_doc
{
"name": "Wireless Headphones",
"price": 99.99,
"category": "electronics",
"created_at": "2026-02-24"
}Bulk Indexing:
POST /_bulk
{ "index": { "_index": "products" } }
{ "name": "Product 1", "price": 10 }
{ "index": { "_index": "products" } }
{ "name": "Product 2", "price": 20 }Use Cases:
Mapping defines how documents are indexed and searched.
Field Types:
# Text - analyzed for full-text search
"name": { "type": "text" }
# Keyword - exact matching, not 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 and normalize text:
PUT /products
{
"settings": {
"analysis": {
"analyzer": {
"custom_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "stop"]
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "custom_analyzer"
}
}
}
}Use Cases:
Elasticsearch provides powerful query DSL.
Match Query - Full-text search with relevance:
GET /products/_search
{
"query": {
"match": {
"name": "wireless headphones"
}
}
}Bool Query - Combine multiple queries:
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "headphones" } }
],
"filter": [
{ "range": { "price": { "lte": 100 } } }
],
"must_not": [
{ "term": { "category": "discontinued" } }
]
}
}
}Range Query - Filter by range:
GET /products/_search
{
"query": {
"range": {
"price": {
"gte": 50,
"lte": 200
}
}
}
}Fuzzy Query - Typo tolerance:
GET /products/_search
{
"query": {
"fuzzy": {
"name": {
"value": "elasticsearch",
"fuzziness": "AUTO"
}
}
}
}Use Cases:
Aggregations enable real-time analytics.
Terms Aggregation - Count by category:
GET /products/_search
{
"size": 0,
"aggs": {
"categories": {
"terms": {
"field": "category",
"size": 10
}
}
}
}Range Aggregation - Group by price range:
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:
GET /products/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month"
}
}
}
}Nested Aggregation - Multi-level analytics:
GET /products/_search
{
"size": 0,
"aggs": {
"categories": {
"terms": {
"field": "category"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}Use Cases:
Elasticsearch scores documents by relevance using TF-IDF and BM25.
TF-IDF (Term Frequency - Inverse Document Frequency):
Score = TF(term) × IDF(term)
TF: How often term appears in document
IDF: How rare term is across all documentsBM25 (Better Matching 25):
Modern algorithm that improves on TF-IDF:
# Elasticsearch uses 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 of certain fields:
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:
Important distinction for performance:
Query - Calculates relevance score (slower):
{ "match": { "name": "headphones" } }Filter - Yes/no match, no scoring (faster):
{ "term": { "category": "electronics" } }Best Practice:
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "headphones" } } # Query - scored
],
"filter": [
{ "range": { "price": { "lte": 100 } } } # Filter - fast
]
}
}
}Efficient pagination for large result sets:
Offset-Based Pagination:
GET /products/_search
{
"from": 20,
"size": 10,
"query": { "match_all": {} }
}Search After - Better for large offsets:
GET /products/_search
{
"size": 10,
"query": { "match_all": {} },
"sort": [{ "created_at": "desc" }],
"search_after": ["2026-02-24T10:00:00"]
}Sorting:
GET /products/_search
{
"query": { "match": { "name": "headphones" } },
"sort": [
{ "price": "asc" },
{ "_score": "desc" }
]
}Show matched terms in results:
GET /products/_search
{
"query": { "match": { "description": "wireless" } },
"highlight": {
"fields": {
"description": {}
}
}
}Provide search suggestions:
GET /products/_search
{
"suggest": {
"product-suggest": {
"prefix": "wire",
"completion": {
"field": "name.completion"
}
}
}
}Index Optimization:
PUT /products
{
"settings": {
"number_of_shards": 3, # Parallelism
"number_of_replicas": 1, # Redundancy
"refresh_interval": "30s" # Batch updates
}
}Query Optimization:
# Use filter instead of query when possible
# Use bool queries efficiently
# Avoid expensive operations (wildcard, regex)
# Use appropriate field typesNow let's build a production-ready e-commerce search platform that demonstrates Elasticsearch patterns. The system handles:
npm i -g @nestjs/cli
nest new ecommerce-search-platform
cd ecommerce-search-platform
npm install @nestjs/elasticsearch @elastic/elasticsearch class-validator class-transformerimport { 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 {}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,
}),
),
};
}
}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();
}
}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,
};
}
}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 {}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:# Start Elasticsearch and Kibana
docker-compose up -d
# Install dependencies
npm install
# Run application
npm run start:dev
# Access Kibana
# http://localhost:5601# Create a product
curl -X POST http://localhost:3000/products \
-H "Content-Type: application/json" \
-d '{
"name": "Wireless Headphones",
"description": "High-quality wireless headphones with noise cancellation",
"price": 99.99,
"category": "electronics",
"tags": ["audio", "wireless", "headphones"],
"rating": 4.5,
"reviews_count": 150,
"in_stock": true
}'
# Create 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 with 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/analyticsQueries are slower than filters for exact matches.
// ❌ Wrong - query for exact match
{ "match": { "category": "electronics" } }
// ✅ Correct - filter for exact match
{ "term": { "category": "electronics" } }Index only fields you need to search.
// ❌ Wrong - index everything
"properties": {
"internal_id": { "type": "text" },
"system_timestamp": { "type": "text" }
}
// ✅ Correct - index only searchable fields
"properties": {
"internal_id": { "type": "keyword", "index": false },
"system_timestamp": { "type": "date", "index": false }
}Wrong field types hurt performance and accuracy.
// ❌ Wrong - price as text
"price": { "type": "text" }
// ✅ Correct - price as number
"price": { "type": "float" }Large offsets are slow. Use search_after instead.
// ❌ Wrong - slow for large offsets
GET /products/_search
{
"from": 100000,
"size": 10
}
// ✅ Correct - use search_after
GET /products/_search
{
"size": 10,
"search_after": ["2026-02-24", "product_id"],
"sort": [{ "created_at": "desc" }, { "_id": "asc" }]
}Elasticsearch operations can fail. Implement proper error handling.
// ✅ 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');
}Monitor index size and performance.
GET /_cat/indices?v
GET /products/_stats
GET /_cluster/healthChoose analyzers based on language and use case.
// ✅ Multi-language support
"name": {
"type": "text",
"fields": {
"english": {
"type": "text",
"analyzer": "english"
},
"spanish": {
"type": "text",
"analyzer": "spanish"
}
}
}Batch index operations for better performance.
// ✅ Bulk indexing
const body = products.flatMap((product) => [
{ index: { _index: 'products', _id: product.id } },
product,
]);
await this.elasticsearchService.bulk({ body });Aliases allow switching indices without downtime.
# Create 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" } }
]
}Use query profiling to identify slow queries.
GET /products/_search
{
"profile": true,
"query": { "match": { "name": "headphones" } }
}Cache frequently accessed data.
// ✅ Cache facets
@Cacheable('product-facets')
async getFacets() {
return this.elasticsearchService.getProductFacets();
}Too many shards hurt performance. Too few limit parallelism.
# Rule of thumb: 1-5 shards per GB of data
# For 100GB: 20-100 shards
# For 1GB: 1-5 shards
PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}Set up monitoring for index health and performance.
# Monitor cluster health
GET /_cluster/health
# Monitor index stats
GET /products/_stats
# Monitor slow queries
GET /_search/stats| Feature | Elasticsearch | Solr | Algolia |
|---|---|---|---|
| Full-Text Search | Excellent | Excellent | Excellent |
| Aggregations | Excellent | Good | Limited |
| Ease of Use | Good | Complex | Very Easy |
| Scalability | Excellent | Good | Managed |
| Cost | Self-hosted | Self-hosted | SaaS |
| Real-Time | Yes | Yes | Yes |
Choose Elasticsearch when:
Choose Algolia when:
Choose Solr when:
Elasticsearch is a powerful search and analytics engine that enables modern applications to provide fast, relevant search experiences. Understanding its core concepts—indexing, querying, aggregations, and scoring—enables you to build scalable search systems.
The e-commerce platform example demonstrates production patterns:
Key takeaways:
Start with simple search use cases. As complexity grows, explore advanced patterns like custom analyzers, machine learning, and cross-cluster search. Elasticsearch's flexibility makes it suitable for systems ranging from simple product search to complex analytics platforms.
Next steps:
Elasticsearch transforms how you think about search—from simple keyword matching to intelligent, relevance-ranked results. Master it, and you'll build search experiences that users love.