[[breaking-changes]]
== Breaking changes coming from the old client

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


[discrete]
=== Don’t panic!

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.


[discrete]
=== Breaking changes

*  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 {es} `v6`, you 
are 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.

* There is no longer 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 only 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.


[source,js]
----
// 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`. 
Every error class has been renamed, and now each is suffixed with `Error` at the 
end.

* Removed errors: `RequestTypeError`, `Generic`, and all the status code 
specific errors (such as `BadRequest` or `NotFound`).

* Added errors: `ConfigurationError` (in case of bad configurations) and 
`ResponseError` that contains all the data you may need to handle the specific 
error, such as `statusCode`, `headers`, `body`, and `message`.


* Renamed errors:

** `RequestTimeout` (408 statusCode) => `TimeoutError`
** `ConnectionFault` => `ConnectionError`
** `NoConnections` => `NoLivingConnectionsError`
** `Serialization` => `SerializationError`
** `Serialization` => `DeserializationError`

* 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 way to do it, via the `node` parameter.

* Certificates are verified by default, if you want to disable certificates verification, you should set the `rejectUnauthorized` option to `false` inside the `ssl` configuration:

[source,js]
----
const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  ssl: { rejectUnauthorized: false }
})
----

* The `plugins` option has been removed. If you want to extend the client now, 
you should use the `client.extend` API.

[source,js]
----
// 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 need to specify them 
in a second option object.

[source,js]
----
// 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 no longer accepts the `query` key. Use the 
`querystring` key instead (which can be a string or an object). You also 
need to send a bulk-like request instead of the `body` key, use the `bulkBody` 
key. In this method, the client specific parameters should be passed as a second 
object.

[source,js]
----
// 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)
})
----

[discrete]
=== Talk is cheap. Show me the code.

You can find a code snippet with the old client below followed by the same code 
logic but with the new client.

[source,js]
----
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.

[source,js]
----
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)
  }
})
----
