ES Kit

A 'pick and mix' library that simplifies writing Elasticsearch code

Overview

ES Kit is an in-Alpha library which makes it easer to get started with Elasticsearch (opens new window).

Elasticsearch is a enterprise-grade document store and search engine based on the Apache Lucene library. It specialises in free text search using an inverted index (opens new window) to score documents against input search queries.

Elasticsearch’s JavaScript Client (opens new window) is used to call its APIs (opens new window) but its inherent flexibility can be overwhelming for beginners:

  • complex request and response formats can lead to verbose, duplicate or fragile application code
  • if you’re new to the API, documentation or terminology, it’s difficult to know what code to write

Library

To address this, ES Kit provides a constrained “kit of parts” in the form of reusable, atomic helper functions which fit together to reliably abstract the API lifecycle, making it quick, easy (and reasonably obvious how) to write clean, trial-and-error-free client code.

The kits comprises 4 main areas:

These modules tread a carefully-planned line between:

  • abstraction – they abstract the request config and response data only
  • knowledge – you’re required to understand the basics of using the client

The helpers themselves address a core subset of the API which creates a much shallower learning curve.

Along with the code, the library ships with what amounts to a beginners guide (opens new window) to Elastic to help new users get up to speed and prevent the library from reinventing the wheel solely for the purpose of making new users productive.

Comparison

As an example of Elastic’s verbosity, here’s a simple search querying an id and the text cat across multiple fields:

import { client } from './client'

// query is verbose with difficult to remember syntax
const params = {
  index: 'contacts',
  query: {
    bool: {
      must: [
        {
          match: {
            groups: req.params.id,
          },
        },
        {
          multi_match: {
            query: req.query.filter,
            fields: [
              '*',
            ],
            type: 'phrase_prefix',
          },
        },
      ],
    },
  },
}

try {
  const res = await client.search(params)
  if (res.body.hits) {
    // response is complex with deeply nested properties
    return res.body.hits.hits.map(hit => {
      return { _id: hit._id, ...hit._source }
    })    
  }
}
catch (err) {
  console.log(err)
  throw err
}

As a new user, it is difficult to know where to start; the whole process is time-consuming and error-prone.

ES Kit provides various ways to simplify this.

Firstly, using utilities (opens new window) which provide typed functions with clear arguments:

import { client } from './client'
import { Queries as _, Helpers as $ } from '@davestewart/es-kit'

// use helpers to build the query
const params = {
  index: 'contacts',
  query: _.must([
    _.match('groups', req.params.id),
    _.multiMatch('*', req.query.filter),
  ])
}

try {
  const res = await client.search(params)
  return $.results(res) // convert results to something usable
}
catch (err) {
  throw $.error(err) // logs and throws a simplified error structure
}

Or, using the Api (opens new window) (which uses the utilities internally) which wraps everything up into one easy call:

import { Api, Helpers as $ } from '@davestewart/es-kit'

// create query, parse results, handle errors, paginate, and more...
return Api.search('contacts', { query: $.query.request(req) })

The approach is designed to let users augment existing code, completely replace it, or find a sweet spot inbetween.

So...

I hope you found this post interesting or inspiring.

If you want to engage further, follow me on Twitter or drop a comment or reaction below.

Either way, thanks for reading!