The elasticsearch and @elastic/elasticsearch packages are both official clients designed to help JavaScript applications communicate with Elasticsearch clusters. The legacy elasticsearch package was the standard for many years, offering a callback-based API that mirrored the REST interface closely. However, it is now deprecated and no longer receives updates or security patches. The @elastic/elasticsearch package is the modern replacement, built from the ground up with TypeScript support, native Promises, and a streamlined architecture that aligns with current Node.js practices. It offers better performance, improved error handling, and a more intuitive developer experience for async/await workflows.
When connecting Node.js applications to Elasticsearch, developers historically relied on the elasticsearch package. Today, that has shifted entirely to @elastic/elasticsearch. While both packages talk to the same database, they differ fundamentally in how they handle code structure, types, and asynchronous operations. Understanding these differences is critical for building stable, maintainable systems.
elasticsearch is the legacy client. It reached its end-of-life and is now deprecated. The maintainers have stopped all development on this package. Using it means your project will not receive fixes for security vulnerabilities or compatibility issues with newer Elasticsearch server versions.
@elastic/elasticsearch is the current, actively maintained client. It was rewritten to leverage modern JavaScript features and provides a robust foundation for production applications. It is designed to work seamlessly with Elasticsearch 7.x and 8.x clusters.
// β Legacy: Deprecated package (Do not install)
const { Client } = require('elasticsearch');
// β
Modern: Active package (Install this)
const { Client } = require('@elastic/elasticsearch');
The most visible difference for developers is how these clients handle asynchronous operations. The legacy client was built when callbacks were the standard in Node.js. The new client embraces Promises and async/await, which makes code easier to read and debug.
elasticsearch relies on callbacks. You must pass a function to handle the result or error. This often leads to nested code or requires wrapping the call in a Promise manually to use modern syntax.
// Legacy: Callback-based API
const client = new elasticsearch.Client({ node: 'http://localhost:9200' });
client.search({
index: 'books',
body: { query: { match: { title: 'javascript' } } }
}, function (error, response) {
if (error) {
console.error('Search failed:', error);
} else {
console.log('Found:', response.hits.hits);
}
});
@elastic/elasticsearch returns a Promise by default. This allows you to use await directly, keeping your logic linear and clean. Error handling uses standard try...catch blocks.
// Modern: Promise-based API
const client = new Client({ node: 'http://localhost:9200' });
async function searchBooks() {
try {
const response = await client.search({
index: 'books',
query: { match: { title: 'javascript' } }
});
console.log('Found:', response.hits.hits);
} catch (error) {
console.error('Search failed:', error);
}
}
For teams using TypeScript or JSDoc, the difference in developer experience is significant. The legacy client has weak or non-existent type definitions, making it hard to catch errors before runtime.
elasticsearch provides minimal type support. You often have to guess parameter names or rely on external community definitions that may be outdated.
// Legacy: No built-in types
// You might misspell 'query' as 'queri' and only find out at runtime
client.search({
index: 'books',
queri: { match_all: {} } // β No TS error here
}, callback);
@elastic/elasticsearch is written in TypeScript and ships with full type definitions. Your IDE will autocomplete parameters, warn you about missing fields, and validate request structures instantly.
// Modern: Full TypeScript support
import { Client, SearchRequest } from '@elastic/elasticsearch';
const client = new Client({ node: 'http://localhost:9200' });
// β
IDE warns if 'query' is missing or malformed
const request: SearchRequest = {
index: 'books',
query: { match: { title: 'typescript' } }
};
const response = await client.search(request);
Setting up the connection also shows the evolution of the API. The legacy client used a complex configuration object that could be confusing. The new client simplifies this with a clearer structure and better defaults for cloud services.
elasticsearch required specific keys like host (often an array) and had verbose options for SSL.
// Legacy: Verbose configuration
const client = new elasticsearch.Client({
host: ['http://localhost:9200', 'http://localhost:9201'],
sniffOnStart: true,
ssl: { rejectUnauthorized: false }
});
@elastic/elasticsearch uses a node property (singular or array) and integrates easily with Elastic Cloud via a cloudId.
// Modern: Clean configuration
const client = new Client({
nodes: ['http://localhost:9200', 'http://localhost:9201'],
// Or for Elastic Cloud:
// cloudId: 'cluster-name:base64string',
// auth: { username: 'elastic', password: 'changeme' }
});
There is a subtle but important change in how search parameters are passed. In the legacy client, the query was nested inside a body object. In the new client, top-level parameters like query, filter, and sort are flattened directly into the main object. This reduces nesting and matches the Elasticsearch JSON API more closely.
elasticsearch wraps the query logic.
// Legacy: Query nested inside 'body'
client.search({
index: 'my-index',
body: {
query: {
match: { status: 'active' }
}
}
}, callback);
@elastic/elasticsearch flattens the structure.
// Modern: Query at the root level
const response = await client.search({
index: 'my-index',
query: {
match: { status: 'active' }
}
});
| Feature | elasticsearch (Legacy) | @elastic/elasticsearch (Modern) |
|---|---|---|
| Status | β Deprecated / End-of-Life | β Actively Maintained |
| Async Model | Callbacks | Promises / Async-Await |
| TypeScript | Poor / Community Types | β First-class Support |
| Query Structure | Nested (body.query) | Flat (query) |
| Cloud Support | Manual Config | β
Native cloudId Support |
| Node Version | Older Node.js versions | Node.js 14+ recommended |
The choice here is straightforward. elasticsearch is a relic of an older era of Node.js development. Keeping it in your stack introduces unnecessary risk and technical debt. @elastic/elasticsearch is the definitive tool for the job. It offers a cleaner API, safety through TypeScript, and the stability of active maintenance.
If you are starting a new project, @elastic/elasticsearch is the only logical choice. If you are maintaining an old system using the legacy client, plan a migration path soon. The shift from callbacks to promises and the flattening of API parameters are small changes in code that yield massive gains in readability and reliability.
Choose @elastic/elasticsearch for all new projects and any active maintenance work. It is the only officially supported client, featuring full TypeScript definitions, native Promise support, and regular security updates. This package is essential if you need reliable long-term support, modern async/await syntax, or integration with current Elasticsearch versions (7.x and 8+).
Do NOT choose elasticsearch for any new development. This package is officially deprecated, meaning it receives no new features, bug fixes, or security updates. The only scenario where you might temporarily keep it is if you are maintaining a legacy codebase that relies heavily on its specific callback-style API and you lack the immediate resources to refactor. Even then, migrating to the new client should be a top priority.
The official Node.js client for Elasticsearch.
If you want to try Elasticsearch and Kibana locally, you can run the following command:
curl -fsSL https://elastic.co/start-local | sh
This will run Elasticsearch at http://localhost:9200 and Kibana at http://localhost:5601.
More information is available here.
Alternatively, you can download the latest version of Elasticsearch manually, or sign-up for a free trial of Elastic Cloud.
Refer to the Installation section of the getting started documentation.
Refer to the Connecting section of the getting started documentation.
The Elasticsearch client is compatible with currently maintained JS versions.
Language clients are forward compatible; meaning that clients support communicating with greater or equal minor versions of Elasticsearch without breaking. It does not mean that the client automatically supports new features of newer Elasticsearch versions; it is only possible after a release of a new client version. For example, a 8.12 client version won't automatically support the new features of the 8.13 version of Elasticsearch, the 8.13 client version is required for that. Elasticsearch language clients are only backwards compatible with default distributions and without guarantees made.
| Elasticsearch Version | Elasticsearch-JS Branch |
|---|---|
| main | main |
| 9.x | 9.x |
| 8.x | 8.x |
| 7.x | 7.x |
NOTE: The minimum supported version of Node.js is v20.
The client versioning follows the Elastic Stack versioning, this means that major, minor, and patch releases are done following a precise schedule that often does not coincide with the Node.js release times.
To avoid support insecure and unsupported versions of Node.js, the client will drop the support of EOL versions of Node.js between minor releases. Typically, as soon as a Node.js version goes into EOL, the client will continue to support that version for at least another minor release. If you are using the client with a version of Node.js that will be unsupported soon, you will see a warning in your logs (the client will start logging the warning with two minors in advance).
Unless you are always using a supported version of Node.js,
we recommend defining the client dependency in your
package.json with the ~ instead of ^. In this way, you will lock the
dependency on the minor release and not the major. (for example, ~7.10.0 instead
of ^7.10.0).
| Node.js Version | Node.js EOL date | End of support |
|---|---|---|
8.x | December 2019 | 7.11 (early 2021) |
10.x | April 2021 | 7.12 (mid 2021) |
12.x | April 2022 | 8.2 (early 2022) |
14.x | April 2023 | 8.8 (early 2023) |
16.x | September 2023 | 8.11 (late 2023) |
18.x | April 2025 | 9.1 (mid 2025) |
[!WARNING] There is no official support for the browser environment. It exposes your Elasticsearch instance to everyone, which could lead to security issues. We recommend that you write a lightweight proxy that uses this client instead, you can see a proxy example here.
If you are using multiple versions of Elasticsearch, you need to use multiple versions of the client. In the past, install multiple versions of the same package was not possible, but with npm v6.9, you can do that via aliasing.
The command you must run to install different version of the client is:
npm install <alias>@npm:@elastic/elasticsearch@<version>
So for example if you need to install 7.x and 6.x, you will run:
npm install es6@npm:@elastic/elasticsearch@6
npm install es7@npm:@elastic/elasticsearch@7
And your package.json will look like the following:
"dependencies": {
"es6": "npm:@elastic/elasticsearch@^6.7.0",
"es7": "npm:@elastic/elasticsearch@^7.0.0"
}
You will require the packages from your code by using the alias you have defined.
const { Client: Client6 } = require('es6')
const { Client: Client7 } = require('es7')
const client6 = new Client6({
cloud: { id: '<cloud-id>' },
auth: { apiKey: 'base64EncodedKey' }
})
const client7 = new Client7({
cloud: { id: '<cloud-id>' },
auth: { apiKey: 'base64EncodedKey' }
})
client6.info().then(console.log, console.log)
client7.info().then(console.log, console.log)
Finally, if you want to install the client for the next version of Elasticsearch (the one that lives in Elasticsearchβs main branch), you can use the following command:
npm install esmain@github:elastic/elasticsearch-js
This software is licensed under the Apache License 2.0.