TL;DR
Elasticsearch APIs power search and analytics at scale. Index JSON documents, query them with the Elasticsearch Query DSL, and use aggregations for analytics. Authenticate with API keys or basic auth. Use Apidog to validate mappings, test search queries, and debug aggregations before deploying to production clusters.
Introduction
Elasticsearch is a distributed search and analytics engine for structured text, logs, metrics, and other document-oriented data.
Common use cases include:
- Full-text search in applications
- Log analysis and debugging
- Real-time analytics dashboards
- Security and network-event analysis
Elasticsearch is part of the ELK stack—Elasticsearch, Logstash, and Kibana—but you can use its REST APIs directly without Logstash.
💡 If you are building search features or log-analysis workflows, Apidog helps you test queries, validate mappings, debug aggregations, save search templates, and share them with your team.
Test Elasticsearch APIs with Apidog - free
By the end of this guide, you will be able to:
- Index and manage documents
- Write Elasticsearch DSL search queries
- Use aggregations for analytics
- Configure mappings and analyzers
- Monitor cluster health
Getting started
Run Elasticsearch locally
Run Elasticsearch in a single-node Docker container:
docker run -p 9200:9200 \
-e "discovery.type=single-node" \
elasticsearch:8.11.0
Alternatively, download Elasticsearch from Elastic.
Verify the installation
Send a request to the root endpoint:
curl -X GET "http://localhost:9200"
Example response:
{
"name": "elasticsearch-1",
"cluster_name": "elasticsearch",
"cluster_uuid": "abc123",
"version": {
"number": "8.11.0",
"build_flavor": "default"
},
"tagline": "You know, for search"
}
Authenticate requests
Elasticsearch 8.x requires authentication by default. Use basic authentication when testing locally:
curl -X GET "http://localhost:9200/_cluster/health" \
-u elastic:your_password
You can also use API keys created in Kibana or through the Elasticsearch API.
Indices and documents
An index is a collection of documents. A document is a JSON object stored in that index.
Create an index
Create a products index with explicit settings and mappings:
curl -X PUT "http://localhost:9200/products" \
-u elastic:your_password \
-H "Content-Type: application/json" \
-d '{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"name": { "type": "text" },
"price": { "type": "float" },
"category": { "type": "keyword" },
"in_stock": { "type": "boolean" },
"created_at": { "type": "date" }
}
}
}'
Use text for analyzed full-text search fields and keyword for exact-match filters, sorting, and aggregations.
Index a document
Add a product document:
curl -X POST "http://localhost:9200/products/_doc" \
-u elastic:your_password \
-H "Content-Type: application/json" \
-d '{
"name": "Wireless Headphones",
"price": 79.99,
"category": "electronics",
"in_stock": true,
"created_at": "2026-03-24T10:00:00Z"
}'
Example response:
{
"_index": "products",
"_id": "abc123",
"_version": 1,
"result": "created",
"_seq_no": 0,
"_primary_term": 1
}
Save the returned _id if you need to retrieve, replace, or delete this document later.
Get a document
Retrieve a document by ID:
curl -X GET "http://localhost:9200/products/_doc/abc123" \
-u elastic:your_password
Update a document
Replace the document at a known ID:
curl -X PUT "http://localhost:9200/products/_doc/abc123" \
-u elastic:your_password \
-H "Content-Type: application/json" \
-d '{
"name": "Wireless Headphones Pro",
"price": 99.99,
"category": "electronics",
"in_stock": true,
"created_at": "2026-03-24T10:00:00Z"
}'
Delete a document
Delete a document by ID:
curl -X DELETE "http://localhost:9200/products/_doc/abc123" \
-u elastic:your_password
Bulk operations
Use the _bulk endpoint to index multiple documents efficiently. Bulk request bodies use newline-delimited JSON (NDJSON):
curl -X POST "http://localhost:9200/products/_bulk" \
-u elastic:your_password \
-H "Content-Type: application/x-ndjson" \
-d '{"index":{"_id":"1"}}
{"name":"Product A","price":10.99,"category":"books","in_stock":true}
{"index":{"_id":"2"}}
{"name":"Product B","price":20.99,"category":"electronics","in_stock":false}
'
Each document needs an action line such as index, followed by its source document.
Search queries
Send search requests to the /_search endpoint.
Basic search
Search for documents whose name matches headphones:
curl -X GET "http://localhost:9200/products/_search" \
-u elastic:your_password \
-H "Content-Type: application/json" \
-d '{
"query": {
"match": {
"name": "headphones"
}
}
}'
Use match for analyzed text fields.
Bool queries
Use a bool query to combine full-text matching with exact filters:
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "headphones"
}
}
],
"filter": [
{
"term": {
"category": "electronics"
}
},
{
"range": {
"price": {
"lte": 100
}
}
},
{
"term": {
"in_stock": true
}
}
]
}
}
}
Use filter for non-scoring constraints such as categories, price ranges, and booleans.
Full-text search with scoring
Search across multiple fields and boost the name field:
{
"query": {
"multi_match": {
"query": "wireless audio headphones",
"fields": ["name^2", "description"],
"type": "best_fields",
"fuzziness": "AUTO"
}
}
}
The ^2 suffix gives name double weight in score calculation.
Phrase search
Find documents containing an exact phrase:
{
"query": {
"match_phrase": {
"description": "noise canceling"
}
}
}
Wildcard and regex-style matching
Use a wildcard query when you need pattern matching:
{
"query": {
"wildcard": {
"name": "*headphone*"
}
}
}
Sort results
Sort products by ascending price, then by descending relevance score:
{
"query": {
"match_all": {}
},
"sort": [
{
"price": "asc"
},
{
"_score": "desc"
}
]
}
Paginate results
Use from and size for offset-based pagination:
{
"from": 20,
"size": 10,
"query": {
"match_all": {}
}
}
This request returns 10 documents starting at result 21.
Aggregations
Aggregations calculate summary statistics across matching documents.
Calculate average price by category
Group products by category, then calculate average, minimum, and maximum prices for each group:
curl -X GET "http://localhost:9200/products/_search" \
-u elastic:your_password \
-H "Content-Type: application/json" \
-d '{
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
},
"min_price": {
"min": {
"field": "price"
}
},
"max_price": {
"max": {
"field": "price"
}
}
}
}
}
}'
Set "size": 0 when you only need aggregation results and do not need matching documents.
Build a price histogram
Bucket products into price intervals:
{
"size": 0,
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 25
}
}
}
}
Group data by date
Use a date histogram for time-series analytics:
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month"
}
}
}
}
Count unique values
Count unique product categories with a cardinality aggregation:
{
"size": 0,
"aggs": {
"unique_categories": {
"cardinality": {
"field": "category"
}
}
}
}
Mappings and analyzers
Mappings define field types and determine how Elasticsearch indexes and searches data.
Choose the right field type
| Type | Use for |
|---|---|
text |
Full-text search, analyzed values |
keyword |
Exact values, filtering, sorting |
integer, float
|
Numbers |
boolean |
True or false values |
date |
Dates and times |
object |
Nested JSON objects |
nested |
Arrays of objects while maintaining relationships |
geo_point |
Latitude and longitude coordinates |
Create a custom analyzer
Use a custom analyzer for specialized text processing, such as autocomplete:
{
"settings": {
"analysis": {
"analyzer": {
"autocomplete": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "autocomplete_filter"]
}
},
"filter": {
"autocomplete_filter": {
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 20
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "autocomplete",
"search_analyzer": "standard"
}
}
}
}
This configuration indexes prefixes for autocomplete while using the standard analyzer when users search.
Cluster management
Check cluster health
curl -X GET "http://localhost:9200/_cluster/health"
Example response:
{
"cluster_name": "elasticsearch",
"status": "green",
"number_of_nodes": 3,
"active_primary_shards": 25
}
Cluster status meanings:
-
green: All shards are allocated. -
yellow: Replica shards are not allocated, which is common on a single-node cluster. -
red: One or more primary shards are missing.
View index statistics
curl -X GET "http://localhost:9200/_cat/indices?v"
View node statistics
curl -X GET "http://localhost:9200/_nodes/stats"
Clear caches
curl -X POST "http://localhost:9200/_cache/clear"
Testing with Apidog
Elasticsearch queries can become complex quickly. Test requests, responses, and environments before using them in production.
1. Save reusable queries
Store parameterized search templates in Apidog:
{
"query": {
"bool": {
"must": [
{
"match": {
"{{search_field}}": "{{search_term}}"
}
}
],
"filter": [
{
"range": {
"{{price_field}}": {
"lte": "{{max_price}}"
}
}
}
]
}
}
}
Use variables to test the same query against different fields, terms, and limits.
2. Validate responses
Add tests to confirm that searches and aggregations return expected data:
pm.test("Search returns results", () => {
const response = pm.response.json()
pm.expect(response.hits.total.value).to.be.above(0)
})
pm.test("Aggregations present", () => {
const response = pm.response.json()
pm.expect(response.aggregations).to.exist
})
3. Separate local and production environments
Store hostnames and credentials as environment variables:
# Local
ES_HOST: http://localhost:9200
ES_USER: elastic
ES_PASSWORD: your_password
# Production
ES_HOST: https://search.yourcompany.com
ES_API_KEY: prod_api_key
Test Elasticsearch APIs with Apidog - free
Common errors and fixes
403 Forbidden
Cause: Authentication failed or the authenticated user lacks required permissions.
Fix: Verify credentials and confirm that the API key has permissions for the target index.
404 index_not_found_exception
Cause: The target index does not exist.
Fix: Create the index first. Auto-creation is enabled by default, but it is not recommended for production.
circuit_breaking_exception
Cause: The query uses too much memory.
Fix: Reduce the size parameter, simplify the query, and add filters to reduce the result set.
search_phase_execution_exception
Cause: The query syntax is invalid.
Fix: Validate the JSON body. Common causes include missing quotes and incorrect field paths.
Alternatives and comparisons
| Feature | Elasticsearch | OpenSearch | Meilisearch | Typesense |
|---|---|---|---|---|
| Setup | Self-hosted | Self-hosted | Single binary | Single binary |
| Search quality | Excellent | Good | Excellent | Good |
| Learning curve | Steep | Steep | Easy | Easy |
| Scalability | Excellent | Excellent | Good | Good |
| Cloud offering | Elastic Cloud | OpenSearch Serverless | Meilisearch Cloud | Typesense Cloud |
Elasticsearch has the broadest feature set and community. Meilisearch and Typesense are simpler options for basic search requirements.
Real-world use cases
E-commerce search
A retail site indexes 100,000 products. Users search product names and descriptions, then filter by category, price range, and availability. Autocomplete suggests products while users type.
Application logs
A DevOps team ships logs to Elasticsearch through Filebeat. Engineers search by service, severity, and time range, while dashboards display error rates and response times.
Security analytics
A security team indexes network logs, searches suspicious IP addresses, visualizes traffic patterns, and alerts on anomalies detected through aggregations.
Wrapping up
You have learned how to:
- Index JSON documents
- Query data with Elasticsearch DSL
- Use aggregations for analytics
- Configure mappings for search behavior
- Monitor cluster health
Next steps:
- Run Elasticsearch locally.
- Create an index with explicit mappings.
- Index test documents.
- Write and test search queries.
- Add aggregations for analytics.
Test Elasticsearch APIs with Apidog - free
FAQ
What is the difference between Elasticsearch and Solr?
Both are Lucene-based search engines. Elasticsearch has a stronger distributed design and API experience. Solr has more enterprise features. Most new projects choose Elasticsearch.
How do I handle special characters in search?
Escape special characters with a backslash:
()[]{}:^\"\\+-!~*?|
You can also use simple_query_string, which is more forgiving.
What is a shard?
Shards are pieces of an index. Each shard is a Lucene index. Primary shards handle writes, while replica shards provide read scaling and fault tolerance.
How many shards should I create?
A common guideline is 20–50 GB per shard. Start with one primary shard and add replicas. Only increase the number of primary shards when needed because you cannot decrease them later.
Can I change mappings after indexing?
Partially. You can add new fields, but changing an existing field type requires reindexing the data. Use index templates to manage mappings consistently.
What is the _routing parameter?
_routing routes documents to specific shards based on a field value. The default is _id. Use routing when queries always filter by a specific field, such as user_id, to improve performance.
How do I handle time-based data?
Use date-based indices, such as:
logs-2026.03.24
This makes it easier to delete old data by removing entire indices and can improve query performance by searching fewer indices.

Top comments (0)