Breaking changes coming from the old clientedit

If you were already using the previous version of this client --i.e. the one you used to install with npm install elasticsearch-- you will encounter some breaking changes.

Don’t panic!edit

Every breaking change was carefully weighed, and each is justified. Furthermore, the new codebase has been rewritten with modern JavaScript and has been carefully designed to be easy to maintain.

Breaking changesedit

  • Minimum supported version of Node.js is v8.
  • Everything has been rewritten using ES6 classes to help users extend the defaults more easily.
  • There is no longer an integrated logger. The client now is an event emitter that emits the following events: request, response, and error.
  • The code is no longer shipped with all the versions of the API, but only that of the package’s major version, This means that if you are using Elasticsearch v6, you will be required to install @elastic/elasticsearch@6, and so on.
  • The internals are completely different, so if you used to tweak them a lot, you will need to refactor your code. The public API should be almost the same.
  • No more browser support, for that will be distributed via another module, @elastic/elasticsearch-browser. This module is intended for Node.js only.
  • The returned value of an API call will no longer be the body, statusCode, and headers for callbacks and just the body for promises. The new returned value will be a unique object containing the body, statusCode, headers, warnings, and meta, for both callback and promises.
// before
const body = await client.search({
  index: 'my-index',
  body: { foo: 'bar' }
})

client.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, (err, body, statusCode, headers) => {
  if (err) console.log(err)
})

// after
const { body, statusCode, headers, warnings } = await client.search({
  index: 'my-index',
  body: { foo: 'bar' }
})

client.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, (err, { body, statusCode, headers, warnings }) => {
  if (err) console.log(err)
})
  • Errors: there is no longer a custom error class for every HTTP status code (such as BadRequest or NotFound). There is instead a single ResponseError. Each error class has been renamed, and now each is suffixed with Error at the end.
  • Errors that have been removed: RequestTypeError, Generic, and all the status code specific errors (such as BadRequest or NotFound).
  • Errors that have been added: ConfigurationError (in case of bad configurations) and ResponseError, which contains all the data you may need to handle the specific error, such as statusCode, headers, body, and message.
  • Errors that has been renamed:

    • RequestTimeout (408 statusCode) ⇒ TimeoutError
    • ConnectionFaultConnectionError
    • NoConnectionsNoLivingConnectionsError
    • SerializationSerializationError
    • SerializationDeserializationError
  • You must specify the port number in the configuration. In the previous version you can specify the host and port in a variety of ways, with the new client there is only one via the node parameter.
  • The plugins option has been removed, if you want to extend the client now you should use the client.extend API.
// before
const { Client } = require('elasticsearch')
const client = new Client({ plugins: [...] })

// after
const { Client } = require('@elastic/elasticsearch')
const client = new Client({ ... })
client.extend(...)
  • There is a clear distinction between the API related parameters and the client related configurations, the parameters ignore, headers, requestTimeout and maxRetries are no longer part of the API object, and you should specify them in a second option object.
// before
const body = await client.search({
  index: 'my-index',
  body: { foo: 'bar' },
  ignore: [404]
})

client.search({
  index: 'my-index',
  body: { foo: 'bar' },
  ignore: [404]
}, (err, body, statusCode, headers) => {
  if (err) console.log(err)
})

// after
const { body, statusCode, headers, warnings } = await client.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, {
  ignore: [404]
})

client.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, {
  ignore: [404]
}, (err, { body, statusCode, headers, warnings }) => {
  if (err) console.log(err)
})
  • The transport.request method will no longer accept the query key, but the querystring key instead (which can be a string or an object), furthermore, you need to send a bulk-like request, instead of the body key, you should use the bulkBody key. Also in this method, the client specific parameters should be passed as a second object.
// before
const body = await client.transport.request({
  method: 'GET',
  path: '/my-index/_search',
  body: { foo: 'bar' },
  query: { bar: 'baz' }
  ignore: [404]
})

client.transport.request({
  method: 'GET',
  path: '/my-index/_search',
  body: { foo: 'bar' },
  query: { bar: 'baz' }
  ignore: [404]
}, (err, body, statusCode, headers) => {
  if (err) console.log(err)
})

// after
const { body, statusCode, headers, warnings } = await client.transport.request({
  method: 'GET',
  path: '/my-index/_search',
  body: { foo: 'bar' },
  querystring: { bar: 'baz' }
}, {
  ignore: [404]
})

client.transport.request({
  method: 'GET',
  path: '/my-index/_search',
  body: { foo: 'bar' },
  querystring: { bar: 'baz' }
}, {
  ignore: [404]
}, (err, { body, statusCode, headers, warnings }) => {
  if (err) console.log(err)
})

Talk is cheap. Show me the code.edit

Following you will find a snippet of code with the old client, followed by the same code logic, but with the new client.

const { Client, errors } = require('elasticsearch')
const client = new Client({
  host: 'http://localhost:9200',
  plugins: [utility]
})

async function run () {
  try {
    const body = await client.search({
      index: 'game-of-thrones',
      body: {
        query: {
          match: { quote: 'winter' }
        }
      }
      ignore: [404]
    })
    console.log(body)
  } catch (err) {
    if (err instanceof errors.BadRequest) {
      console.log('Bad request')
    } else {
      console.log(err)
    }
  }
}

function utility (Client, config, components) {
  const ca = components.clientAction.factory
  Client.prototype.utility = components.clientAction.namespaceFactory()
  const utility = Client.prototype.utility.prototype

  utility.index = ca({
    params: {
      refresh: {
        type: 'enum',
        options: [
          'true',
          'false',
          'wait_for',
          ''
        ]
      },
    },
    urls: [
      {
        fmt: '/<%=index%>/_doc',
        req: {
          index: {
            type: 'string',
            required: true
          }
        }
      }
    ],
    needBody: true,
    method: 'POST'
  })
})

And now with the new client.

const { Client, errors } = require('@elastic/elasticsearch')
// NOTE: `host` has been renamed to `node`,
//       and `plugins` is no longer supported
const client = new Client({ node: 'http://localhost:9200' })

async function run () {
  try {
    // NOTE: we are using the destructuring assignment
    const { body } = await client.search({
      index: 'game-of-thrones',
      body: {
        query: {
          match: { quote: 'winter' }
        }
      }
    // NOTE: `ignore` now is in a separated object
    }, {
      ignore: [404]
    })
    console.log(body)
  } catch (err) {
    // NOTE: we are checking the `statusCode` property
    if (err.statusCode === 400) {
      console.log('Bad request')
    } else {
      console.log(err)
    }
  }
}

// NOTE: we can still extend the client, but with  a different API.
//       This new API is a little bit more verbose, since you must write
//       your own validations, but it's way more flexible.
client.extend('utility.index', ({ makeRequest, ConfigurationError }) => {
  return function utilityIndex (params, options) {
    const { body, index, ...querystring } = params
    if (body == null) throw new ConfigurationError('Missing body')
    if (index == null) throw new ConfigurationError('Missing index')
    const requestParams = {
      method: 'POST',
      path: `/${index}/_doc`,
      body: body,
      querystring
    }
    return makeRequest(requestParams, options)
  }
})