NestJS OpenSearch Module
The `nestjs-opensearch` package provides a dedicated module for integrating OpenSearch with the NestJS framework. It simplifies connecting to OpenSearch clusters, offering configurations for single, multiple, and asynchronously provisioned clients. The current stable version is 1.4.1, which includes support for NestJS v11 and OpenSearch client v3. Releases tend to align with major NestJS and `@opensearch-project/opensearch` peer dependency updates to maintain compatibility. Its key differentiators include leveraging NestJS's declarative module structure, providing robust dependency injection for OpenSearch clients (both default and named), and supporting both synchronous and asynchronous configuration patterns, including `useFactory` and `useClass` options. This design allows for flexible integration within various NestJS application architectures, adhering to the framework's modularity and inversion of control principles.
Common errors
-
Error: Nest can't resolve dependencies of the OpensearchService (?). Please make sure that the argument OpensearchClient at index [0] is available in the SearchModule context.
cause The `OpensearchModule` was not correctly imported or configured in the NestJS module where `OpensearchService` is provided or injected.fixEnsure `OpensearchModule.forRoot(...)` or `OpensearchModule.forRootAsync(...)` is added to the `imports` array of the respective NestJS module (e.g., `AppModule`). -
TypeError: (0 , nestjs_opensearch_1.OpensearchModule).forRootAsync is not a function
cause This usually indicates attempting to call `forRootAsync` with an array of configurations after it was deprecated, or a mismatch in how the method is expected to be called.fixSince v0.3.0, `forRootAsync` no longer accepts an array. Call `OpensearchModule.forRootAsync()` separately for each individual client configuration. Also, verify you are using a compatible version of the library. -
Error: Cannot find module '@opensearch-project/opensearch' or its corresponding type declarations.
cause The core `@opensearch-project/opensearch` client library, a peer dependency, has not been installed in your project.fixInstall the necessary peer dependency: `npm install @opensearch-project/opensearch` or `yarn add @opensearch-project/opensearch`. -
OpensearchClientError: No Living connections
cause The OpenSearch client failed to establish a connection to the specified node(s). This can be due to an incorrect node URL, network issues, or authentication failures.fixVerify the `node` URL(s) provided in `OpensearchModule` configuration (e.g., `http://localhost:9200`). Check network connectivity to the OpenSearch cluster and ensure any authentication credentials (username, password, API key) are correct.
Warnings
- deprecated Passing an array of configurations to `OpensearchModule.forRootAsync()` has been deprecated since v0.3.0 and is slated for removal in future major releases. This will become a breaking change.
- gotcha This module has strict peer dependency requirements for `@nestjs/common` and `@opensearch-project/opensearch`. Ensure your project's versions of these packages fall within the supported ranges (e.g., NestJS v8-v11, OpenSearch client v1-v3 for v1.4.1) to avoid runtime errors or unexpected behavior.
- gotcha Type compatibility issues with TypeScript v5 were fixed in version v0.3.1. Users on older versions of `nestjs-opensearch` might experience compilation errors if using TypeScript v5 or newer.
- gotcha When configuring multiple OpenSearch clients, each client must be assigned a unique `clientName` property. Failing to do so for `forRoot()` or `forRootAsync()` calls can lead to client configurations being overwritten, resulting in unexpected behavior or incorrect client injection.
Install
-
npm install nestjs-opensearch -
yarn add nestjs-opensearch -
pnpm add nestjs-opensearch
Imports
- OpensearchModule
const OpensearchModule = require('nestjs-opensearch').OpensearchModule;import { OpensearchModule } from 'nestjs-opensearch'; - InjectOpensearchClient
import InjectOpensearchClient from 'nestjs-opensearch';
import { InjectOpensearchClient } from 'nestjs-opensearch'; - OpensearchClient
import { OpensearchClient } from 'nestjs-opensearch'; - OpensearchClientOptionsFactory
import { OpensearchClientOptionsFactory } from 'nestjs-opensearch';
Quickstart
import { Module, Injectable } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { OpensearchModule, InjectOpensearchClient, OpensearchClient } from 'nestjs-opensearch';
// A basic configuration factory for NestJS ConfigModule
function configuration() {
return {
opensearch: {
node: process.env.OPENSEARCH_NODE || 'http://localhost:9200',
auth: {
username: process.env.OPENSEARCH_USERNAME || 'admin',
password: process.env.OPENSEARCH_PASSWORD || 'admin'
}
}
};
}
@Injectable()
export class SearchService {
constructor(
// Inject the default client (no clientName specified)
@InjectOpensearchClient() private readonly defaultClient: OpensearchClient,
// Inject a named client
@InjectOpensearchClient('myNamedClient') private readonly namedClient: OpensearchClient,
) {}
async indexDocument(index: string, id: string, document: any): Promise<void> {
await this.defaultClient.index({
index,
id,
body: document,
refresh: true,
});
console.log(`Document '${id}' indexed successfully using the default client.`);
}
async searchDocuments(index: string, query: any): Promise<any[]> {
const { body } = await this.namedClient.search({
index,
body: { query },
});
console.log(`Search performed on '${index}' using 'myNamedClient'.`);
return body.hits.hits;
}
}
@Module({
imports: [
// Load configuration for the application
ConfigModule.forRoot({
load: [configuration],
isGlobal: true,
}),
// Configure the default OpenSearch client asynchronously
OpensearchModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
node: configService.get<string>('opensearch.node'),
auth: configService.get<{ username: string; password: string }>('opensearch.auth'),
}),
}),
// Configure a named OpenSearch client asynchronously
OpensearchModule.forRootAsync({
clientName: 'myNamedClient',
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
node: configService.get<string>('opensearch.node'),
auth: configService.get<{ username: string; password: string }>('opensearch.auth'),
}),
}),
],
providers: [SearchService],
exports: [SearchService],
})
export class AppModule {}
// To bootstrap and run this application:
// import { NestFactory } from '@nestjs/core';
// async function bootstrap() {
// const app = await NestFactory.create(AppModule);
// await app.listen(3000);
// const searchService = app.get(SearchService);
// await searchService.indexDocument('my-test-index', 'doc1', { title: 'Hello World' });
// const results = await searchService.searchDocuments('my-test-index', { match_all: {} });
// console.log('Search results:', results);
// }
// bootstrap();