elasticsearch-fetch
v0.9.1
Published
A lightweight, fetch-based alternative to the heavy Elasticsearch SDK
Maintainers
Readme
elasticsearch-fetch
elasticsearch-fetch is a pure ESM, 11 times smaller fetch-based alternative to the Elasticsearch SDK (on npm as @elastic/elasticsearch). It works on any modern ESM runtime that supports fetch including Node.js 18+, Deno, Bun, and Edge Worker runtimes (e.g. Cloudflare Workers).
npm install elasticsearch-fetchTable of Contents
- File Sizes
- Usage Option 1: Full SDK
- Usage Option 2: Functional Tree-Shaken
- Usage Option 3 (Recommended): Custom SDK
- Presets
- Auth
- Connection Options
- TypeScript Types
- All Operations
- How it Works
- Contributions
Usage
There are three ways to use elasticsearch-fetch depending on whether you prefer compatibility or build size.
File Sizes
elasticsearch-fetch can range anywhere between 4kb and 22kb minzipped in your build. But compare that to @elastic/elasticsearch's build size of 257kb minzipped. That's a bundle size savings of 92% to 98%!
Option 1 - Full SDK - 22kb minzipped
You can use elasticsearch-fetch with all 567 of the operations available in the @elastic/elasticsearch SDK:
import { Client } from 'elasticsearch-fetch';
const client = new Client({
node: 'http://localhost:9200',
auth: {
username: process.env.ES_USERNAME,
password: process.env.ES_PASSWORD,
},
});
await client.indices.create({
index: 'tweets',
mappings: {
properties: {
id: { type: 'integer' },
text: { type: 'text' },
user: { type: 'keyword' },
time: { type: 'date' }
}
}
}, { ignore: [400] });
const bulkResponse = await client.bulk({ refresh: true, operations });
const count = await client.count({ index: 'tweets' });Option 2 - Functional tree-shaken: your build includes only what you use
import { createTransport, indicesCreate, bulk, count } from 'elasticsearch-fetch/functions';
const tx = createTransport({
node: 'http://localhost:9200',
auth: {
username: process.env.ES_USERNAME,
password: process.env.ES_PASSWORD,
},
});
await indicesCreate(tx, {
index: 'tweets',
mappings: {
properties: {
id: { type: 'integer' },
text: { type: 'text' },
user: { type: 'keyword' },
time: { type: 'date' }
}
}
}, { ignore: [400] });
const bulkResponse = await bulk(tx, { refresh: true, operations });
const count = await count(tx, { index: 'tweets' });Option 3 (Recommended) - Custom SDK: build your own client from presets and/or individual endpoints
Register exactly the endpoints you want, and client is typed to match — only the
registered methods autocomplete and type-check.
import { Client, presetCrud, presetEql } from 'elasticsearch-fetch/presets';
import { clusterHealth, clusterInfo } from 'elasticsearch-fetch/functions';
const client = new Client(
{
node: 'http://localhost:9200',
auth: {
username: process.env.ES_USERNAME,
password: process.env.ES_PASSWORD,
},
},
{
...presetCrud,
...presetEql,
'cluster.health': clusterHealth,
'cluster.info': clusterInfo,
}
);
await client.bulk({ refresh: true, operations });
await client.eql.search({ index: 'tweets', query: 'process where true' });
await client.cluster.health();You can also register additional endpoints after construction with
client.register({ ... }), though methods added this way won't be reflected
in client's TypeScript type.
Presets
Import any combination of the following from elasticsearch-fetch/presets
- presetCrud - All 39 CRUD operations
- presetSchema -
info, all 27cat.*operations, and all 71indices.*operations - presetTasks - all 3
tasks.*operations - presetIngest - all 5
ingest.*operations - presetEql - all 4
eql.*operations - presetEsql - all 15
esql.*operations - presetSql - all 6
sql.*operations - presetAsyncSearch - all 4
asyncSearch.*operations
Example minzipped build sizes:
- Monolithic
@elastic/elasticsearch- 257kb - Full
elasticsearch-fetch- 22kb - presetCrud - 4kb
- presetCrud + presetSchema - 7kb
Auth
Auth is compatible with the official SDK. You can specify auth in 7 different ways.
auth: undefined- Server requires no authauth: { username: 'user', password: 'pass' }- Username and passwordauth: { bearer: 'abc123' }- Bearer tokenauth: { apiKey: 'abc123' }- API Keyprocess.env.ELASTICSEARCH_USERNAME && process.env.ELASTICSEARCH_PASSWORD- Env vars for username and passwordprocess.env.ELASTICSEARCH_BEARER- Env var for bearer tokenprocess.env.ELASTICSEARCH_API_KEY- Env var for API key
Connection Options
node is optional when creating a Client or a transport with createTransport().
If omitted, it falls back to process.env.ELASTICSEARCH_NODE, and if that's not set
either, it defaults to http://localhost:9200.
import { Client } from 'elasticsearch-fetch';
// equivalent to new Client({ node: process.env.ELASTICSEARCH_NODE ?? 'http://localhost:9200' })
const client = new Client();TypeScript Types
Every operation in elasticsearch-fetch—whether you use the full SDK, a
tree-shaken function, or a custom preset-built client—is typed using the
same request/response interfaces as @elastic/elasticsearch, exposed under
its estypes namespace. elasticsearch-fetch doesn't redefine or duplicate
Elasticsearch's types; it imports estypes as a type-only import and reuses
it directly, so @elastic/elasticsearch only needs to be present for its
type definitions — none of its runtime code ends up in your bundle.
In practice this means you get the same editor autocomplete and compile-time checking you'd get from the official SDK:
import { Client } from 'elasticsearch-fetch';
const client = new Client({ node: 'http://localhost:9200' });
// TypeScript knows the shape of `count`'s params...
await client.count({ index: 'tweets', min_score: 1.5 });
// ...and flags mistakes before you run the code
await client.count({ index: 'tweets', min_score: '1.5' }); // Error: string is not assignable to number
await client.count({ indx: 'tweets' }); // Error: `indx` does not exist; did you mean `index`?
// The resolved response is typed too, so properties autocomplete
const result = await client.count({ index: 'tweets' });
console.log(result.count); // numberEach generated function is typed as (transport, params: estypes.XRequest, options?) => Promise<estypes.XResponse>
for its operation (for example count's params/response types are
estypes.CountRequest/estypes.CountResponse). When you build a client from
Client, presets, or register(), those per-function types flow through
into the shape of client, so only the methods you actually registered
appear on it, each with its own accurately typed params and response —
there's no generic "any operation" signature to fall back on.
All Operations
For a list of all methods with associated links to Elasticsearch's online documentation see one of the following:
How it Works
Elasticsearch releases a schema file on GitHub that includes all server methods. They generate the JavaScript SDK programmatically based on that schema. Each generated function includes runtime validation which takes up a lot of space. For instance the TypeScript source code of bulk is 2768 bytes in @elastic/elasticsearch, but 352 bytes in elasticsearch-fetch/functions.
So elasticsearch-fetch includes a small fetch-based transport layer instead of the node-networking transport layer of @elastic/elasticsearch. But it also builds its dist from Elasticsearch's own schema file.
Contributions
Contributions through GitHub issues and pull requests are welcome.
Bun is required for testing and building the elasticsearch-fetch package.
