OriginChain docs
examples · elasticsearch

Elasticsearch examples

← All examples

Copy-paste recipes for the Elasticsearch API, using the official @elastic client. Each one reads from or writes to /v1/tenants/:t/es/. New here? Set the client up in Connect an Elasticsearch client, or see the raw endpoints in the HTTP API reference.

These use the Node client; the same requests work from any Elasticsearch 7.x client or straight over HTTP. Writes commit with the row, so a document is searchable immediately, and row-level security and column masking apply to every query.

1Create an index with a mapping

Declare the fields up front. Types are the Elasticsearch types your client already sends.

await es.indices.create({
  index: 'shop.products',
  mappings: { properties: {
    name:  { type: 'text' },
    brand: { type: 'keyword' },
    price: { type: 'integer' }
  } }
})

2Index a document

Create or replace a document by _id. It is searchable the instant the call returns — there is no refresh interval to wait on.

await es.index({
  index: 'shop.products',
  id: 'sku-8842',
  document: { name: 'Carbon Marathon', brand: 'Aero', price: 149 }
})

3Bulk-load many documents

The fast path for ingest and backfills. One NDJSON action line per document (the client builds it for you from the operations array).

await es.bulk({ operations: [
  { index: { _index: 'shop.products', _id: 'sku-1207' } },
  { name: 'Trail 24',    brand: 'Aero',  price: 89 },
  { index: { _index: 'shop.products', _id: 'sku-3355' } },
  { name: 'City Runner', brand: 'Metro', price: 72 }
]})

4Partial update (merge)

Send only the fields that change. Everything else on the document is preserved.

await es.update({
  index: 'shop.products',
  id: 'sku-8842',
  doc: { price: 139 }   // name, brand, ... untouched
})

5Full-text match

Run the Query DSL you already write. match analyzes the query the same way the field was indexed.

await es.search({
  index: 'shop.products',
  query: { match: { name: 'marathon' } }
})

6Bool query with a filter

Combine a scoring must with non-scoring filter clauses — a term and a numeric range.

await es.search({
  index: 'shop.products',
  query: { bool: {
    must:   [{ match: { name: 'runner' } }],
    filter: [{ term: { brand: 'Metro' } },
             { range: { price: { lte: 100 } } }]
  } }
})

7Aggregate (terms)

Set size: 0 to skip the hits and get just the buckets — the shape Kibana and Grafana panels use.

await es.search({
  index: 'shop.products',
  size: 0,
  aggs: { by_brand: { terms: { field: 'brand' } } }
})

8Count matches

A count without the documents.

await es.count({
  index: 'shop.products',
  query: { term: { brand: 'Aero' } }
})

9Paginate with search_after

Deep paging within the 10,000-hit window. Sort by a field plus _id, then pass the previous page’s last sort tuple.

const page1 = await es.search({
  index: 'shop.products', size: 20,
  sort: [{ price: 'asc' }, { _id: 'asc' }],
  query: { match_all: {} }
})

const last = page1.hits.hits.at(-1).sort   // e.g. [72, 'sku-3355']
await es.search({
  index: 'shop.products', size: 20,
  sort: [{ price: 'asc' }, { _id: 'asc' }],
  search_after: last,
  query: { match_all: {} }
})

10Delete by query (bounded)

max_docs caps how many are deleted. An unbounded whole-table delete is refused rather than run — use _reindex into a fresh index for that.

await es.deleteByQuery({
  index: 'shop.products',
  max_docs: 100,
  query: { term: { brand: 'Aero' } }
})