Generated the following references: - `entities` - `file` - `fulfillment` - `inventory` - `js_client` - `medusa` - `medusa_config` - `medusa_react` - `modules` - `notification` - `payment` - `price_selection` - `pricing` - `product` - `search` - `services` - `stock_location` - `tax` - `tax_calculation` - `types` - `workflows` Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Shahed Nasser <27354907+shahednasser@users.noreply.github.com>
428 lines
17 KiB
Plaintext
428 lines
17 KiB
Plaintext
---
|
||
displayed_sidebar: core
|
||
slug: /development/search/create
|
||
---
|
||
|
||
import ParameterTypes from "@site/src/components/ParameterTypes"
|
||
|
||
# How to Create a Search Service
|
||
|
||
In this document, you’ll learn how to create a search service in the Medusa backend and the methods you must implement in it.
|
||
|
||
## Prerequisites
|
||
|
||
A search service must extend the `AbstractSearchService` class imported from the `@medusajs/utils` package. If you don’t already have the package
|
||
installed, run the following command to install it within your project:
|
||
|
||
```bash npm2yarn
|
||
npm install @medusajs/utils
|
||
```
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
A search service class is in a TypeScript or JavaScript file created in the `src/services` directory. The class must extend the `AbstractSearchService` class imported
|
||
from the `@medusajs/utils` package.
|
||
|
||
Based on services’ naming conventions, the file’s name should be the slug version of the search service’s name without `service`, and the class’s name should be the
|
||
pascal case of the search service’s name following by `Service`.
|
||
|
||
For example, create the `MySearchService` class in the file `src/services/my-search.ts`:
|
||
|
||
```ts title="src/services/my-search.ts"
|
||
import { AbstractSearchService } from "@medusajs/utils"
|
||
|
||
class MySearchService extends AbstractSearchService {
|
||
isDefault = false
|
||
|
||
createIndex(indexName: string, options: Record<string, any>) {
|
||
throw new Error("Method not implemented.")
|
||
}
|
||
getIndex(indexName: string) {
|
||
throw new Error("Method not implemented.")
|
||
}
|
||
addDocuments(
|
||
indexName: string,
|
||
documents: Record<string, any>[],
|
||
type: string
|
||
) {
|
||
throw new Error("Method not implemented.")
|
||
}
|
||
replaceDocuments(
|
||
indexName: string,
|
||
documents: Record<string, any>[],
|
||
type: string
|
||
) {
|
||
throw new Error("Method not implemented.")
|
||
}
|
||
deleteDocument(
|
||
indexName: string,
|
||
document_id: string | number
|
||
) {
|
||
throw new Error("Method not implemented.")
|
||
}
|
||
deleteAllDocuments(indexName: string) {
|
||
throw new Error("Method not implemented.")
|
||
}
|
||
search(
|
||
indexName: string,
|
||
query: string,
|
||
options: Record<string, any>
|
||
) {
|
||
return {
|
||
message: "test",
|
||
}
|
||
}
|
||
updateSettings(
|
||
indexName: string,
|
||
settings: Record<string, any>
|
||
) {
|
||
throw new Error("Method not implemented.")
|
||
}
|
||
|
||
}
|
||
|
||
export default MySearchService
|
||
```
|
||
|
||
---
|
||
|
||
## Notes About Class Methods
|
||
|
||
Although there are several helper methods in this class, the main methods used by the Medusa backend are `addDocuments`, `deleteDocument`, and `search`.
|
||
The rest of the methods are provided in case you need them for custom use cases.
|
||
|
||
---
|
||
|
||
## constructor
|
||
|
||
You can use the `constructor` of your search service to access the different services in Medusa through dependency injection.
|
||
|
||
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
|
||
you can initialize it in the constructor and use it in other methods in the service.
|
||
|
||
Additionally, if you’re creating your search service as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin,
|
||
you can access them in the constructor. The default constructor already sets the value of the class proeprty `options_` to the passed options.
|
||
|
||
### Example
|
||
|
||
```ts
|
||
// ...
|
||
import { ProductService } from "@medusajs/medusa"
|
||
|
||
type InjectedDependencies = {
|
||
productService: ProductService
|
||
}
|
||
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
protected readonly productService_: ProductService
|
||
|
||
constructor({ productService }: InjectedDependencies) {
|
||
// @ts-expect-error prefer-rest-params
|
||
super(...arguments)
|
||
this.productService_ = productService
|
||
|
||
// you can also initialize a client that
|
||
// communicates with a third-party service.
|
||
this.client = new Client(options)
|
||
}
|
||
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"container","type":"`any`","description":"An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"options","type":"`any`","description":"If this search service is created in a plugin, the plugin's options are passed in this parameter.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="new AbstractSearchService"/>
|
||
|
||
___
|
||
|
||
## Properties
|
||
|
||
<ParameterTypes parameters={[{"name":"isDefault","type":"`any`","description":"This property is used to pinpoint the default search service defined in the Medusa core. For custom search services, the `isDefault` property must be `false`.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"options_","type":"`Record<string, unknown>`","description":"If your search service is created in a plugin, the plugin's options will be available in this property.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="AbstractSearchService"/>
|
||
|
||
___
|
||
|
||
## Methods
|
||
|
||
### createIndex
|
||
|
||
This method is used to create an index in the search engine.
|
||
|
||
#### Example
|
||
|
||
An example implementation, assuming `client` would interact with a third-party service:
|
||
|
||
```ts title="src/services/my-search.ts"
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
createIndex(indexName: string, options: Record<string, any>) {
|
||
return this.client_.initIndex(indexName)
|
||
}
|
||
// ...
|
||
}
|
||
```
|
||
|
||
Another example of how the [MeiliSearch plugin](https://docs.medusajs.com/plugins/search/meilisearch) uses the
|
||
`options` parameter:
|
||
|
||
```ts
|
||
class MeiliSearchService extends AbstractSearchService {
|
||
// ...
|
||
async createIndex(
|
||
indexName: string,
|
||
options: Record<string, unknown> = { primaryKey: "id" }
|
||
) {
|
||
return await this.client_.createIndex(indexName, options)
|
||
}
|
||
// ...
|
||
}
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"indexName","type":"`string`","description":"The name of the index to create.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"options","type":"`unknown`","description":"Any options that may be relevant to your search service. This parameter doesn't have \nany defined format as it depends on your custom implementation.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="createIndex"/>
|
||
|
||
#### Returns
|
||
|
||
<ParameterTypes parameters={[{"name":"unknown","type":"`unknown`","optional":false,"defaultValue":"","description":"No required format of returned data, as it depends on your custom implementation.","expandable":false,"children":[]}]} sectionTitle="createIndex"/>
|
||
|
||
### getIndex
|
||
|
||
This method is used to retrieve an index’s results from the search engine.
|
||
|
||
#### Example
|
||
|
||
```ts
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
|
||
getIndex(indexName: string) {
|
||
return this.client_.getIndex(indexName)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"indexName","type":"`string`","description":"The name of the index","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="getIndex"/>
|
||
|
||
#### Returns
|
||
|
||
<ParameterTypes parameters={[{"name":"unknown","type":"`unknown`","optional":false,"defaultValue":"","description":"No required format of returned data, as it depends on your custom implementation.","expandable":false,"children":[]}]} sectionTitle="getIndex"/>
|
||
|
||
### addDocuments
|
||
|
||
This method is used to add a document to an index in the search engine.
|
||
|
||
When the Medusa backend loads, it triggers indexing for all products available in the Medusa backend, which uses this method to add or update documents.
|
||
It’s also used whenever a new product is added or a product is updated.
|
||
|
||
#### Example
|
||
|
||
```ts
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
|
||
async addDocuments(
|
||
indexName: string,
|
||
documents: Record<string, any>[],
|
||
type: string
|
||
) {
|
||
return await this.client_
|
||
.addDocuments(indexName, documents)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"indexName","type":"`string`","description":"The name of the index to add the documents to.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"documents","type":"`unknown`","description":"The list of documents to add. For example, an array of [products](../../entities/classes/entities.Product.mdx).","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"type","type":"`string`","description":"The type of documents being indexed. For example, `products`.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="addDocuments"/>
|
||
|
||
#### Returns
|
||
|
||
<ParameterTypes parameters={[{"name":"unknown","type":"`unknown`","optional":false,"defaultValue":"","description":"The response of saving the documents in the search engine, but there’s no required format of the response.","expandable":false,"children":[]}]} sectionTitle="addDocuments"/>
|
||
|
||
### replaceDocuments
|
||
|
||
This method is used to replace existing documents in the search engine of an index with new documents.
|
||
|
||
#### Example
|
||
|
||
```ts
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
|
||
async replaceDocuments(
|
||
indexName: string,
|
||
documents: Record<string, any>[],
|
||
type: string
|
||
) {
|
||
await this.client_
|
||
.removeDocuments(indexName)
|
||
return await this.client_
|
||
.addDocuments(indexName, documents)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"indexName","type":"`string`","description":"The name of the index that the documents belong to.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"documents","type":"`unknown`","description":"The list of documents to index. For example, it can be an array of [products](../../entities/classes/entities.Product.mdx).\nBased on your search engine implementation, the documents should include an identification key that allows replacing the existing documents.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"type","type":"`string`","description":"The type of documents being replaced. For example, `products`.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="replaceDocuments"/>
|
||
|
||
#### Returns
|
||
|
||
<ParameterTypes parameters={[{"name":"unknown","type":"`unknown`","optional":false,"defaultValue":"","description":"The response of replacing the documents in the search engine, but there’s no required format of the response.","expandable":false,"children":[]}]} sectionTitle="replaceDocuments"/>
|
||
|
||
### deleteDocument
|
||
|
||
This method is used to delete a document from an index.
|
||
|
||
When a product is deleted in the Medusa backend, this method is used to delete the product from the search engine’s index.
|
||
|
||
#### Example
|
||
|
||
```ts
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
|
||
async deleteDocument(
|
||
indexName: string,
|
||
document_id: string | number
|
||
) {
|
||
return await this.client_
|
||
.deleteDocument(indexName, document_id)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"indexName","type":"`string`","description":"The name of the index that the document belongs to.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"document_id","type":"`string` \\| `number`","description":"The ID of the item indexed. For example, if the deleted item is a product, then this is\nthe ID of the product.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="deleteDocument"/>
|
||
|
||
#### Returns
|
||
|
||
<ParameterTypes parameters={[{"name":"unknown","type":"`unknown`","optional":false,"defaultValue":"","description":"The response of deleting the document in the search engine, but there’s no required format of the response.","expandable":false,"children":[]}]} sectionTitle="deleteDocument"/>
|
||
|
||
### deleteAllDocuments
|
||
|
||
This method is used to delete all documents from an index.
|
||
|
||
#### Example
|
||
|
||
```ts
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
|
||
async deleteAllDocuments(indexName: string) {
|
||
return await this.client_
|
||
.deleteDocuments(indexName)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"indexName","type":"`string`","description":"The index's name.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="deleteAllDocuments"/>
|
||
|
||
#### Returns
|
||
|
||
<ParameterTypes parameters={[{"name":"unknown","type":"`unknown`","optional":false,"defaultValue":"","description":"The response of deleting the documents of that index in the search engine, but there’s no required format of the response.","expandable":false,"children":[]}]} sectionTitle="deleteAllDocuments"/>
|
||
|
||
### search
|
||
|
||
This method is used to search through an index by a query.
|
||
|
||
In the Medusa backend, this method is used within the [Search Products API Route](https://docs.medusajs.com/api/store#products\_postproductssearch)
|
||
to retrieve the search results. The API route's response type is an array of items, though the item's format is not defined as it depends on the
|
||
data returned by this method.
|
||
|
||
#### Example
|
||
|
||
```ts
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
|
||
async search(
|
||
indexName: string,
|
||
query: string,
|
||
options: Record<string, any>
|
||
) {
|
||
const hits = await this.client_
|
||
.search(indexName, query)
|
||
return {
|
||
hits,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"indexName","type":"`string`","description":"The index's name. In the case of the Search Products API Routes, its value is `products`.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"query","type":"`null` \\| `string`","description":"The search query to retrieve results for.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"options","type":"`unknown`","description":"Options that can configure the search process. The Search Products API route passes an object having the properties:\n\n- `paginationOptions`: An object having an `offset` and `limit` properties, which are passed in the API Route's body.\n- `filter`: Filters that are passed in the API Route's request body. Its format is unknown, so you can pass filters based on your search service.\n- `additionalOptions`: Any other parameters that may be passed in the request's body.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="search"/>
|
||
|
||
#### Returns
|
||
|
||
<ParameterTypes parameters={[{"name":"unknown","type":"`unknown`","optional":false,"defaultValue":"","description":"The list of results. For example, an array of products.","expandable":false,"children":[]}]} sectionTitle="search"/>
|
||
|
||
### updateSettings
|
||
|
||
This method is used to update the settings of an index within the search service. This is useful if you want to update the index settings when the plugin options change.
|
||
|
||
For example, in the Algolia plugin, a loader, which runs when the Medusa backend loads, is used to update the settings of indices based on the plugin options.
|
||
The loader uses this method to update the settings.
|
||
|
||
#### Example
|
||
|
||
```ts
|
||
class MySearchService extends AbstractSearchService {
|
||
// ...
|
||
|
||
async updateSettings(
|
||
indexName: string,
|
||
settings: Record<string, any>
|
||
) {
|
||
return await this.client_
|
||
.updateSettings(indexName, settings)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
<ParameterTypes parameters={[{"name":"indexName","type":"`string`","description":"The index's name to update its settings.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"settings","type":"`unknown`","description":"The settings to update. Its format depends on your use case.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} sectionTitle="updateSettings"/>
|
||
|
||
#### Returns
|
||
|
||
<ParameterTypes parameters={[{"name":"unknown","type":"`unknown`","optional":false,"defaultValue":"","description":"The response of updating the index in the search engine, but there’s no required format of the response.","expandable":false,"children":[]}]} sectionTitle="updateSettings"/>
|
||
|
||
---
|
||
|
||
## Test Implementation
|
||
|
||
:::note
|
||
|
||
If you created your search service in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||
|
||
:::
|
||
|
||
After finishing your search service implementation:
|
||
|
||
1\. Make sure you have an [event bus module](https://docs.medusajs.com/development/events/modules/redis) installed that triggers search indexing when the Medusa backend loads.
|
||
|
||
2\. Run the `build` command in the root of your Medusa backend:
|
||
|
||
```bash npm2yarn
|
||
npm run build
|
||
```
|
||
|
||
3\. Start the backend with the `develop` command:
|
||
|
||
```bash
|
||
npx medusa develop
|
||
```
|
||
|
||
4\. Try to search for products either using the [Search Products API Route](https://docs.medusajs.com/api/store#products_postproductssearch). The results returned by your search service's `search` method is returned in the `hits` response field.
|