Creating and managing indexes with settings and mappings, dynamic vs explicit mapping, index templates and aliases, then document CRUD operations: index, get, update, delete, up to the bulk API, routing, and versioning.

In episode 3 your Elasticsearch is up and running. Now we start using it for the most fundamental thing: storing data and retrieving it back. The concepts in this episode — index, document, CRUD, bulk — are the everyday language you'll use in all upcoming episodes, so understand them well. Episode 4 covers two areas: index management (creating indexes with settings and mappings, index templates, and aliases) and document operations (adding, reading, updating, and deleting documents, including the bulk API for batches, plus the routing and versioning concepts).
Indexes are created with PUT and a lowercase name. By default, Elasticsearch auto-indexes all fields (dynamic mapping), but for full control we specify settings and mapping explicitly:
PUT /produk{
"settings": { "number_of_shards": 3, "number_of_replicas": 1, "refresh_interval": "1s" },
"mappings": {
"properties": { "name": { "type": "text" }, "price": { "type": "float" } }
}
}In Dev Tools, the format above is written directly. With curl, combine it with -X PUT -H 'Content-Type: application/json' -d '{...}'. Note number_of_shards: this value cannot be changed after the index is created — you can only change it via reindex (episode 13).
Dynamic mapping makes Elasticsearch guess the data type from the first value that comes in: strings become text plus keyword, numbers become long/float, booleans become boolean. This guessing is practical for prototyping, but dangerous in production — a single number like a product ID can be incorrectly indexed as long when it should be keyword. That's why production always uses explicit mapping, and we can set the dynamic policy with "dynamic": "strict" to reject unknown fields. Mapping details are covered thoroughly in episode 5.
A template is a "blueprint" applied automatically to new indexes that match a name pattern:
{
"index_patterns": ["logs-*"],
"template": {
"settings": { "number_of_shards": 2, "number_of_replicas": 1 },
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"message": { "type": "text" }
}
}
}
}With this template, every new index named logs-... automatically gets consistent settings and mapping — without having to write them one by one. This is the foundation of index management as code (episode 28).
An alias is an alternative name for one or more indexes. This provides great flexibility: the application always points to the alias, while the index behind it can be swapped without changing code — when a reindex happens (episode 13), the alias makes the move to a new index feel instant to the application:
POST /_aliases{
"actions": [
{ "add": { "index": "produk", "alias": "produk-search" } },
{ "remove": { "index": "produk", "alias": "produk-old" } }
]
}Adding a document is done with POST (let Elasticsearch determine the _id) or PUT (specify the _id yourself):
PUT /produk/_doc/1{ "name": "Kaos Polos Premium", "price": 99000 }The response contains _id, _version (starting at 1), and result set to created. Running the same PUT again will overwrite the document with result: updated and _version: 2 — this is upsert behavior.
GET /produk/_doc/1{
"_index": "produk",
"_id": "1",
"_version": 2,
"found": true,
"_source": { "name": "Kaos Polos Premium", "price": 99000 }
}If you don't want to load the entire _source (which can be large), use the _source filter:
GET /produk/_doc/1?_source=nameUpdate changes only the fields given without overwriting the entire document:
POST /produk/_update/1{ "doc": { "price": 85000 } }Other fields remain intact, _version increments, and result becomes updated. Note: POST /produk/_update/1 is a scriptable operation — later in episode 13 we'll use Update By Query to change many documents at once.
DELETE /produk/_doc/1DELETE /produkBe careful with DELETE /produk — the index and all its data are permanently lost. There's no undo button (except snapshots, episode 20).
Sending requests one by one is wasteful — every request carries HTTP overhead. The Bulk API lets you send many operations in a single request to POST /produk/_bulk. The format is: two lines per operation — an action line (index/create/update/delete) and a data line (except for delete):
{ "index": { "_id": "1" } }
{ "name": "Kaos Polos Premium", "price": 99000 }
{ "update": { "_id": "2" } }
{ "doc": { "price": 75000 } }Bulk is the fastest way to write mass data — we'll use it for high-performance indexing in episode 19. An ideal bulk request contains several thousand documents totaling tens of megabytes, and each action is independent: one failure doesn't stop the others.
Routing determines which shard hosts a document. By default, Elasticsearch computes a hash of the _id. You can set manual routing so documents in the same category (for example category: fashion) always land on the same shard — useful for searches often filtered by category:
PUT /produk/_doc/3?routing=fashionWith manual routing, a search can limit itself to a single shard (GET /produk/_search?routing=fashion), speeding up queries that already know the target category. The trade-off: data distribution becomes uneven if the routing is poor.
Versioning is Elasticsearch's optimistic locking system. Every document has a _version that increments with each change. You can send ?version= to prevent accidental overwrites — if the sent version doesn't match, Elasticsearch rejects the request with a 409 Conflict status. This keeps parallel write operations safe.
Tip
From now on, adopt the working pattern: templates for consistency, aliases for flexibility, and stable _ids for data that gets updated. Good index names use a domain-year.month.day pattern for time-series data (for example produk-logs-2026.08.03) — this will help a lot when we cover data streams and ILM in episodes 10 and 11.
In episode 4 you mastered index management and document operations: creating indexes with settings and mappings, understanding dynamic vs explicit mapping, using index templates and aliases, then running full CRUD — PUT to add/overwrite, GET to read, POST _update to change parts, DELETE to remove — plus the bulk API for batches, routing to control distribution, and versioning to prevent write conflicts.
Key takeaways:
number_of_shards cannot be changed after an index is created.PUT /_doc/1 is upsert; POST _update only changes the fields mentioned.Next we dive deeper into the layer that determines how your data is searched: mapping and data types. In episode 5 we'll dissect the difference between text vs keyword, numeric types, date, boolean, object and nested, geo, IP, as well as the mapping parameters that govern each field's behavior. See you there!