Search with the Query DSL
The Query DSL you already write works unchanged: match and match_phrase for analyzed text, term and terms for exact values, plus range, bool and fuzziness. Deep paging is search_after within a 10,000-hit window, and anything this engine cannot answer faithfully is refused rather than approximated.
Full-text: match and match_phrase
match analyzes your terms the same way the field was analyzed and ranks by BM25. match_phrase keeps the words adjacent.
query: { match: { description: 'waterproof jacket' } }
// only this exact phrase, in this order
query: { match_phrase: { description: 'waterproof jacket' } }Exact: term and terms
Use these on keyword, numeric, date and boolean fields. On an analyzed text field a term query looks for a single analyzed token, which is rarely what you want.
query: { term: { brand: 'Aero' } }
query: { terms: { brand: ['Aero', 'Metro'] } }Ranges
query: { range: { price: { gte: 50, lt: 200 } } }
query: { range: { added: { gte: '2026-09-01T00:00:00Z' } } }Combining: bool
must contributes to the score, filter does not (and is the one to use for exact predicates), should boosts, must_not excludes.
query: { bool: {
must: [{ match: { name: 'marathon' } }],
filter: [{ term: { brand: 'Aero' } },
{ range: { price: { lte: 200 } } }],
should: [{ match: { description: 'carbon' } }],
must_not: [{ term: { discontinued: true } }]
} }Typo tolerance
Fuzziness walks the words actually indexed in the field. For a search box, pair it with the suggester so you can show what you corrected to.
query: { match: { name: { query: 'marathn', fuzziness: 'AUTO' } } }Sorting, paging and trimming the response
sort: [{ price: 'desc' }, '_score'],
from: 0, size: 20,
_source: ['name', 'price'] // only these fields come backFor deep paging use search_after with the sort values of the last hit, within a 10,000-hit window. Point-in-Time and scroll are not offered: both hold a snapshot this engine does not take, and pretending otherwise would hand you a page that quietly drifts.
Reading one document
When you know the id, skip the search. The version that comes back is the one the write reported, so you can carry it into a conditional write.
await es.get({ index: 'shop.products', id: 'sku-8842' })
await es.exists({ index: 'shop.products', id: 'sku-8842' }) // true / false
await es.mget({ index: 'shop.products', body: { ids: ['a','b'] } })What a missing index does
A search or count against an index that does not exist answers 404 index_not_found_exception, the same as a real cluster. An index that exists but holds nothing answers 200 with zero hits. Those are different answers on purpose: a client polling for an index can tell absent from empty.
Security is part of the query
Row-level security and column masking are enforced on the search itself. A caller who cannot see a row will not find it through any query, and a query that searches a masked column is refused rather than answered — otherwise the match set could be used to reconstruct the value the mask hides.
Elasticsearch is a trademark of Elasticsearch B.V., registered in the U.S. and in other countries. OriginChainDB is not affiliated with, endorsed by, or sponsored by Elasticsearch B.V. OriginChainDB implements a compatible HTTP API so that existing Elasticsearch clients can talk to it; it does not distribute Elasticsearch software.