How to connect to AWS Elasticsearch using the Elasticsearch JavaScript SDK?

For the elasticsearch.client, you can use http-aws-es for connectionClass and amazonES with keys.

var client = new elasticsearch.Client({
    hosts: config.elasticsearch.host,
    connectionClass: require('http-aws-es'),
    amazonES: {
        region: config.aws.region,
        accessKey: config.aws.key,
        secretKey: config.aws.secret
    }
});

Here's an implementation of the Connection class I've been using with TypeScript:

import { Connection as UnsignedConnection } from '@elastic/elasticsearch';
import * as AWS from 'aws-sdk';
import RequestSigner from 'aws-sdk/lib/signers/v4';
import { ClientRequest, IncomingMessage } from 'http';

class AwsElasticsearchError extends Error {}
type RequestOptions = Parameters<UnsignedConnection['request']>[0];


class AwsSignedConnection extends UnsignedConnection {
  public request(
    params: RequestOptions,
    callback: (err: Error | null, response: IncomingMessage | null) => void,
  ): ClientRequest {
    const signedParams = this.signParams(params);
    return super.request(signedParams, callback);
  }

  private signParams(params: RequestOptions): RequestOptions {
    const region = AWS.config.region || process.env.AWS_DEFAULT_REGION;
    if (!region) throw new AwsElasticsearchError('missing region configuration');
    if (!params.method) throw new AwsElasticsearchError('missing request method');
    if (!params.path) throw new AwsElasticsearchError('missing request path');
    if (!params.headers) throw new AwsElasticsearchError('missing request headers');

    const endpoint = new AWS.Endpoint(this.url.href);
    const request = new AWS.HttpRequest(endpoint, region);

    request.method = params.method;
    request.path = params.querystring
      ? `${params.path}/?${params.querystring}`
      : params.path;
    request.body = params.body;

    Object.entries(params.headers).forEach(([header, value]) => {
      if (value === undefined) return;
      if (typeof value === 'string') request.headers[header] = value;
      else if (typeof value === 'number') request.headers[header] = `${value}`;
      else request.headers[header] = value.join('; ');
    });
    request.headers.Host = endpoint.host;

    const signer = new RequestSigner(request, 'es');
    signer.addAuthorization(AWS.config.credentials, new Date());
    return request;
  }
}

export { AwsSignedConnection, UnsignedConnection, AwsElasticsearchError };

Then you can provide it only if credentials are available, so you can use it to point to a local (e.g. Docker) Elasticsearch without credentials:

import awsSdk from 'aws-sdk';
import elasticsearch from '@elastic/elasticsearch';
import { AwsSignedConnection, UnsignedConnection } from '../aws-es-connector';

client = new elasticsearch.Client({
  Connection: awsSdk.config.credentials ? AwsSignedConnection : UnsignedConnection,
  node: elasticsearchEndpoint,
});

NPM package elasticsearch has been deprecated, and replaced by @elastic/elasticsearch

So instead of using http-aws-es, which is supposed to work with the deprecated elasticsearch package, you can consider using package @acuris/aws-es-connection, an AWS ES connection for the new elasticsearch client @elastic/elasticsearch. It works well for me in my project. You can find its usage in its readme file, but here is a piece of sample code:

import {
  createAWSConnection,
  awsGetCredentials,
} from '@acuris/aws-es-connection';
import { Client } from '@elastic/elasticsearch';

export const getESClient = async () => {
  const esEndpoint = process.env.AWS_ES_ENDPOINT;
  if (!esEndpoint) {
    throw new Error(
      'AWS_ES_ENDPOINT ENV not set.'
    );
  }

  const awsCredentials = await awsGetCredentials();
  const AWSConnection = createAWSConnection(awsCredentials);
  const client = new Client({
    ...AWSConnection,
    node: esEndpoint,
  });
  return client;
};

export const createNewIndex = async (index: string) => {
  try {
    const client = await getESClient();
    const exists = await client.indices.exists({ index });
    if (!exists || !exists.statusCode || exists.statusCode !== 404) {
      console.log(`Index ${index} might alrady exist.`, exists);
      return false;
    }
    const created = await client.indices.create({
      index,
      body: {
        mappings: {
          properties: {
            product_id: {
              type: 'keyword',
            },
            product_description: {
              type: 'text',
            },
          },
        },
      },
    });
    console.log(`Index created for ${index}`, created);
  } catch (error) {
    console.log(`Error creating index ${index}`, error);
    return false;
  }
  return true;
};