Adding a search engine
Evolve’s three-layer architecture separates schema definitions from
implementation. The catalog module defines search interfaces (product search,
facets, categories) in @evolve-framework/schemas, while the actual search
logic lives in an implementation package. This guide walks through creating a
new implementation for a search engine such as Typesense, Meilisearch, or
Elasticsearch.
-
Create the implementation package
Create a new package in your project repository that depends on the core and schemas layers:
Directorypackages/search-typesense/
Directorysrc/
- index.ts
- module.ts
- search-provider.ts
Directorymappers/
- product.ts
- client.ts
- package.json
{"name": "@evolve-packages/search-typesense","dependencies": {"@evolve-framework/core": "catalog:","@evolve-framework/schemas": "catalog:","@evolve-framework/commercetools": "catalog:","typesense": "^1.8.0"}}The
@evolve-framework/*packages are published to the private registry and pinned centrally inpnpm-workspace.yamlundercatalog:, so the package uses thecatalog:protocol instead of version numbers. -
Implement a SearchProvider
The catalog module delegates search to a
SearchProviderabstraction, defined in@evolve-framework/commercetools(src/lib/search/base.ts), with the built-in Algolia and commercetools providers as reference implementations. Create a provider with the same shape that implements search against your engine:src/search-provider.ts export class TypesenseSearchProvider {async searchProducts(context, args) {const client = this.createClient(context.config);const results = await client.collections("products").documents().search({q: args.query,filter_by: buildFilters(args.filters, context.storeContext),sort_by: mapSortOrder(args.sort),page: args.page,per_page: args.pageSize,facet_by: args.facets?.join(","),});return {results: results.hits.map(mapSearchHitToProduct),total: results.found,facets: mapFacets(results.facet_counts),};}async searchCategories(context, args) {// Implement category search}private createClient(config: Record<string, unknown>) {return new Typesense.Client({nodes: [{ host: config.typesenseHost, port: 443, protocol: "https" }],apiKey: config.typesenseApiKey,});}}Note that the
resultsfield (notitems) andfacetsfield match theSearchResulttype defined alongside theSearchProviderabstraction. -
Create the GraphQL module
Subclass
CatalogModuleand override the search-related resolvers.CatalogModulebuilds its resolver map in its constructor, so replace theproductSearchresolver after callingsuper:src/module.ts import {CatalogModule,type ClientFactory,} from "@evolve-framework/commercetools";import { TypesenseSearchProvider } from "./search-provider.ts";export class TypesenseCatalogModule extends CatalogModule {constructor(options: { clientFactory: ClientFactory }) {super(options);const provider = new TypesenseSearchProvider();this.resolvers.Query.productSearch = async (_parent, args, context) => {return provider.searchProducts(context, args);};}}This replaces only the search resolvers. Product detail pages, categories, and other catalog queries continue to use the default commercetools resolvers.
-
Wire into the catalog service
In the catalog service’s
module.ts, swap the default module for your new one:backend/services/catalog-commercetools/src/module.ts import { CompositeModule } from "@evolve-framework/core";import { TypesenseCatalogModule } from "@evolve-packages/search-typesense";import { clientFactory } from "#src/commercetools.ts";export const createModule = (): CompositeModule => {return new CompositeModule([new TypesenseCatalogModule({ clientFactory })]);};The search engine configuration goes through the per-request
configobject built in the service’ssrc/context.ts:backend/services/catalog-commercetools/src/context.ts config: {searchEngine: "typesense",typesenseHost: config.TYPESENSE_HOST,typesenseApiKey: config.TYPESENSE_API_KEY,},This is exposed as
context.configso your search provider can access the credentials at request time. -
Sync product data
Search engines need a product feed. Evolve publishes
product-publishedevents when products change. Create an event handler that indexes products into your search engine. See custom events for how to wire a handler. -
Add credentials to Terraform
Store the search engine API key and host as secrets and inject them as environment variables. Follow the patterns in the existing Algolia configuration in
terraform/.
Further reading
Section titled “Further reading”- Framework architecture for the three-layer design and module system
- Customization for overriding resolvers and subclassing modules
- Messaging and events for product sync through the event bus

