I like my ElasticSearch a la Node.js

how-to
Sep 21, 201321 mins

While ElasticSearch is easy enough to work with via its RESTful HTTP API, there are myriad client libraries available in almost every conceivable programming language. If Node.js is your language of choice, then there’s at least two actively supported libraries available.

My favorite is dubbed, albeit rather dully, ”Elastic Search Client”, but don’t let the library’s unimaginative name fool you: this is a handy library that allows you to do everything you could do via cURL with the added benefit of JavaScript callbacks. Best of all, you can use the Node Elastic Search Client in Coffeescript, which is a handy language that makes JavaScript less verbose and that ultimately compiles into JavaScript.

Accordingly, if you’re familiar with the typical RESTful API calls for creating and mapping indexes, plus indexing and searching documents, then you’ll find Elastic Search Client easy enough to pick up.

To get started, add the library as a dependency in your NPM package.json file like so:

My package.json file that lists the latest version of elasticsearchclient.
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<code class='json'><span class='line'><span class="s2">"dependencies"</span><span class="err">:</span><span class="p">{</span>
</span><span class='line'>  <span class="nt">"elasticsearchclient"</span> <span class="p">:</span> <span class="s2">"latest"</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">"mocha"</span> <span class="p">:</span> <span class="s2">"latest"</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">"should"</span> <span class="p">:</span> <span class="s2">"latest"</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">"coffee-script"</span> <span class="p">:</span> <span class="s2">"latest"</span>
</span><span class='line'><span class="p">}</span>
</span>

Since I happen to prefer CoffeeScript over JavaScript, I’ve also included CoffeeScript as a dependency.

Like any Node library, you’ll need to include a library it via a require statement to make use of it. In this case, I’ll require 'elasticsearchclient' and then connect to a local instance like so:

Initalizing a new client
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<code class='javascript'><span class='line'><span class="nx">ElasticSearchClient</span> <span class="o">=</span> <span class="nx">require</span> <span class="s1">'elasticsearchclient'</span>
</span><span class='line'><span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ElasticSearchClient</span> <span class="p">{</span> <span class="nx">host</span><span class="o">:</span> <span class="s1">'localhost'</span><span class="p">,</span> <span class="nx">port</span><span class="o">:</span> <span class="mi">9200</span> <span class="p">}</span>
</span>

Going forward, I’m going to reference a few variables, namely indexName, which is “beer_recipes” and objName, which is “beer”. What’s more, this code is using Mocha and should, so that’ll explain the various specification related statements in the examples below.

With a connection to an Elasticsearch server, I can consequently create an index like so:

Creating an index in a before clause
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<code class='javascript'><span class='line'><span class="nx">describe</span> <span class="s1">'Create and update an index'</span><span class="p">,</span> <span class="o">-></span>
</span><span class='line'>  <span class="nx">before</span> <span class="p">(</span><span class="nx">done</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>      <span class="nx">client</span><span class="p">.</span><span class="nx">createIndex</span><span class="p">(</span><span class="nx">indexName</span><span class="p">).</span><span class="nx">on</span> <span class="s1">'data'</span><span class="p">,</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>          <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="nx">data</span>
</span><span class='line'>          <span class="nx">data</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">be</span><span class="p">.</span><span class="nx">ok</span>
</span><span class='line'>          <span class="nx">done</span><span class="p">()</span>
</span><span class='line'>      <span class="p">.</span><span class="nx">exec</span><span class="p">()</span>
</span>

And I can update the index’s mapping too. Just use the putMapping call:

Updating an Index mapping
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<code class='javascript'><span class='line'><span class="nx">it</span> <span class="s1">'should support a mapping put which changes the analyzer'</span><span class="p">,</span> <span class="p">(</span><span class="nx">done</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>  <span class="nx">snowball</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"mappings"</span> <span class="o">:</span> <span class="p">{</span> <span class="s2">"beer"</span> <span class="o">:</span> <span class="p">{</span> <span class="s2">"properties"</span> <span class="o">:</span> <span class="p">{</span> <span class="s2">"ingredients"</span> <span class="o">:</span> <span class="p">{</span> <span class="s2">"type"</span> <span class="o">:</span> <span class="s2">"string"</span><span class="p">,</span> <span class="s2">"analyzer"</span> <span class="o">:</span> <span class="s2">"snowball"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}}</span>
</span><span class='line'>  <span class="nx">client</span><span class="p">.</span><span class="nx">putMapping</span><span class="p">(</span><span class="nx">indexName</span><span class="p">,</span> <span class="nx">objName</span><span class="p">,</span> <span class="nx">snowball</span><span class="p">).</span><span class="nx">on</span> <span class="s1">'data'</span><span class="p">,</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>      <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="nx">data</span>
</span><span class='line'>      <span class="nx">data</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">be</span><span class="p">.</span><span class="nx">ok</span>
</span><span class='line'>      <span class="nx">done</span><span class="p">()</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">exec</span><span class="p">()</span>
</span>

In this case, the actual mapping JSON document is identical to what I’d have to pass via cURL, for instance.

You should start to notice a pattern with respect to how the Elastic Search Client deals with callbacks – using an on method, you can register a callback for 'data', which essentially entails the response from the server; moreover, you can also register a listener for 'error', which as you can imagine, gets invoked if there is a problem.

Deleting an index is just as easy as creating one too:

Deleting an index
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<code class='javascript'><span class='line'><span class="nx">after</span> <span class="p">(</span><span class="nx">done</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>  <span class="nx">client</span><span class="p">.</span><span class="nx">deleteIndex</span><span class="p">(</span><span class="nx">indexName</span><span class="p">).</span><span class="nx">on</span> <span class="s1">'data'</span><span class="p">,</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>      <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="nx">data</span>
</span><span class='line'>      <span class="nx">data</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">be</span><span class="p">.</span><span class="nx">ok</span>
</span><span class='line'>      <span class="nx">done</span><span class="p">()</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">exec</span><span class="p">()</span>
</span>

To index a document, you simply use the index function like so:

Indexing a document
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<code class='javascript'><span class='line'><span class="nx">beer</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"name"</span><span class="o">:</span> <span class="s2">"Todd Enders' Witbier"</span><span class="p">,</span> <span class="s2">"style"</span><span class="o">:</span> <span class="s2">"wit, Belgian ale, wheat beer"</span><span class="p">,</span> <span class="s2">"ingredients"</span><span class="o">:</span> <span class="s2">"4.0 ..."</span><span class="p">}</span>
</span><span class='line'><span class="nx">client</span><span class="p">.</span><span class="nx">index</span><span class="p">(</span><span class="nx">indexName</span><span class="p">,</span> <span class="nx">objName</span><span class="p">,</span> <span class="nx">beer</span><span class="p">).</span><span class="nx">on</span> <span class="s1">'data'</span><span class="p">,</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>  <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="nx">data</span>
</span><span class='line'>  <span class="nx">data</span><span class="p">.</span><span class="nx">ok</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span> <span class="kc">true</span>
</span><span class='line'><span class="p">.</span><span class="nx">exec</span><span class="p">()</span>
</span>

Note in this case, I pass along an index name, and object name, and a JSON document to index. Because JSON marries so nicely with JavaScript, this library is quite nice, don’t you think?

Searching is just as easy (as I’m sure you already guessed). You create a query JSON document and pass it along to the search method like so:

Searching is just as easy!
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<code class='javascript'><span class='line'><span class="nx">iquery</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"query"</span> <span class="o">:</span> <span class="p">{</span> <span class="s2">"term"</span> <span class="o">:</span> <span class="p">{</span> <span class="s2">"ingredients"</span> <span class="o">:</span> <span class="s2">"lemons"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
</span><span class='line'><span class="nx">client</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">indexName</span><span class="p">,</span> <span class="nx">objName</span><span class="p">,</span> <span class="nx">iquery</span><span class="p">).</span><span class="nx">on</span> <span class="s1">'data'</span><span class="p">,</span> <span class="p">(</span><span class="nx">idata</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>  <span class="nx">result</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="nx">idata</span>
</span><span class='line'>  <span class="nx">result</span><span class="p">.</span><span class="nx">hits</span><span class="p">.</span><span class="nx">total</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span> <span class="mi">1</span>
</span><span class='line'>  <span class="nx">done</span><span class="p">()</span>
</span><span class='line'><span class="p">.</span><span class="nx">exec</span><span class="p">()</span>
</span>

The resultant response from the server is a JSON document that must be parsed and dealt with accordingly.

Of course, you can grab an individual document via its id just as easily:

Getting an individual document via its document id
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<code class='javascript'><span class='line'><span class="nx">client</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="nx">indexName</span><span class="p">,</span> <span class="nx">objName</span><span class="p">,</span> <span class="nx">doc_id</span><span class="p">).</span><span class="nx">on</span> <span class="s1">'data'</span><span class="p">,</span> <span class="p">(</span><span class="nx">getData</span><span class="p">)</span> <span class="o">-></span>
</span><span class='line'>  <span class="nx">doc</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span> <span class="nx">getData</span>
</span><span class='line'>  <span class="nx">doc</span><span class="p">.</span><span class="nx">_source</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">should</span><span class="p">.</span><span class="nx">equal</span> <span class="s2">"Todd Enders' Witbier"</span>
</span><span class='line'>  <span class="nx">done</span><span class="p">()</span>
</span><span class='line'><span class="p">.</span><span class="nx">exec</span><span class="p">()</span>
</span>

Node’s Elastic Search Client is a breeze to pick up; plus, the natural union of JSON and JavaScript make Node a perfect language for interfacing with ElasticSearch. Dig it?

andrew_glover

When Andrew Glover isn't listening to “Funkytown” or “Le Freak” he enjoys speaking on the No Fluff Just Stuff Tour. He also writes articles for multiple online publications including IBM's developerWorks and O'Reilly’s ONJava and ONLamp portals. Andrew is also the co-author of Java Testing Patterns, which was published by Wiley in September 2004; Addison-Wesley’s Continuous Integration; and Manning’s Groovy in Action.

More from this author