The much talked about Coveo Headless

Blog | Technical
Written By: Nisha MagnaniPublished On: Apr 13 2023
coveo-headless-part-1

I have been working on a Sitecore project where I had to develop the front end for the search functionality using Coveo, and I spent quite some time looking through the documentation to answer some of the challenging questions that I had. I was finally able to find a solution, and I thought I’d document my findings in hopes they would help other developers working on similar projects involving Sitecore headless/Coveo headless.

What is it?

It is a library to develop Coveo powered UI components. It works as a bridge between UI elements and Coveo. Coveo is an enterprise-class native cloud SaaS/PaaS solution that provides a unified and secure way to search for contextually relevant content across many enterprise systems.

How it works?

It consists of an engine whose main property is its state. This state in turn depends on various features. To access these features, various controllers are provided. A few of which we will see in this blog.

We have been using Coveo Headless API with Typescript for this project. So, all the code examples will be in the same language.

Why have we decided to use it?

Coveo is one of the top search solutions in the market. It stands out from other solutions because it indexes items directly in the CMS rather than scanning the public site. Coveo also has AI based relevancy and recommendations, something that is popular among clients. In addition, Coveo’s Headless API is recent and modern and won’t de deprecated anytime soon.

Jumping into the Implementation

Headless generally have two basic building blocks:

  1. The controllers => Direct actions to the engine depending on user inputs
  2. The engine => controls the state of the search interface and interacts with the Coveo Platform. 

First, install Headless library npm install @coveo/headless

Prerequisite: Node.js v12.0 or higher is required

Then, configure a Headless Engine using one of the builder functions. Below the search endpoint configuration will be specified which will define where to send Coveo search requests and the authentication related information.

import {buildSearchEngine} from '@coveo/headless';

export const searchEngine = (
  organizationId: string,
  searchApiKey: string,
  cloudPlatformUri: string
) => {
  return buildSearchEngine({
    configuration: {
      organizationId: organizationId,
      accessToken: searchApiKey,
      platformUrl: cloudPlatformUri,
    },
  });
};

organizationID => is a unique identifier of the configured Coveo Organization

searchApiKey => this is important to have the granted access levels on Execute Queries domain along with Push access levels on Analytics Data domain.

Here we are using cloudPlatformUri to pass the Coveo cloud platform URI as we were facing issue because our client has configured it in Europe region URL for which was https://platform-eu.cloud.coveo.com/ and Coveo by default uses platform.coveo.com. Due to this, we were getting exceptions in the console, and the requests were failing. The code below will create an instance of search engine that can be used whenever a search is performed, to pass the query and other search related information/configurations.

Usage of code above

import {SearchEngine } from '@coveo/headless';
import { searchEngine } from 'components/Foundation/Search/Coveo/SearchEngine';

let newSearchEngine: SearchEngine;

const search = ({
  sitecoreContext: { coveoInfo },
}: SearchProps): JSX.Element => {

 if (newSearchEngine === undefined) {
    newSearchEngine = searchEngine(
      coveoInfo.organizationId,
      coveoInfo.searchApiKey,
      coveoInfo.cloudPlatformUri
    );
  }

return (
    <div>
</div>
);
};

export default withDatasourceCheck()<SearchProps>(
  withSitecoreContext()(Search)
);

In our project we are following the Helix structure, so all the basic building blocks defining Coveo are under the
Also, we are fetching all the Coveo platform related information from Sitecore. We have it passed via layout service.

Lastly, it is highly recommended and a best practice to have withDatasourceCheck to be included as it prevents the component from showing runtime errors/yellow screen of death (YSOD).

Build Search Box using a Controller

Search box rendering triggers search and related query suggestion calls to the Search API through REST endpoint proxy. This interface includes a text that user inputs and submit the queries. In the code below, an instance of search box is created using buildSearchBox controller. Additionally, search box options are passed which can be seen in the usage of the code.

import { buildSearchBox, SearchBoxOptions, SearchEngine, SearchBox } from '@coveo/headless';

export type SearchBoxProps = {
  options: SearchBoxOptions;
  searchEngine: SearchEngine;
};
export type SearchBoxController = SearchBox;
export const builtSearchBox = ({ options, searchEngine }: SearchBoxProps) => {
  return buildSearchBox(searchEngine, { options });
};

Usage of the code above

The code below shows how to create an instance of a search box using search box options such as id, numberOfSuggestions. By default, it is 0 which means no recommendation will be displayed.

import { builtSearchBox } from 'components/Foundation/Search/Coveo/SearchBox';
  
const searchBox = builtSearchBox({
    searchEngine: newSearchEngine,
    options: {
      id: 'navigationSearchBox',
      numberOfSuggestions: fields?.NumberOfRecommendedSearchItems?.value || 0,
    },
  });

id => to differentiate search box from another search box on the same page.

numberOfSuggestions => this is to configure number of recommended items to be shown below the search box.

Controller responsible for showing Query results

The ResultList headless controller is responsible for displaying query results. This also offers an interface for designing a common result list UI controller. Whereas buildResultList is used to create ResultList controller instance. It takes two main arguments – search engine (we saw in the start of this blog), and ResultListProps which includes ResultListOptions. This includes an array of string called fieldsToInclude. Here fieldsToInclude should have custom Coveo fields that you want to display in the results. In case it is empty, then default fields will only be included. Additionally, using the instance one can fetch the current state like isLoading also the returned results fetched after the query has been executed.

import { buildResultList, ResultListOptions, SearchEngine } from '@coveo/headless';

type ResultListProps = {
  searchEngine: SearchEngine;
  options: ResultListOptions;
};

export const builtResultList = ({ searchEngine, options }: ResultListProps) => {
  return buildResultList(searchEngine, { options });
};

Usage of the code above

In the code below an instance of result list is created and it will explicitly include Title field in the returned results (if any).

import { builtResultList } from 'components/Foundation/Search/Coveo/ResultList';
const resultList = builtResultList({
    searchEngine: newSearchEngine,
    options: {
      fieldsToInclude: [
        'title',
      ],
    },
  });

Controller responsible for returning search status

SearchStatus basically depicts what the status of the search performed is, for example, is has any errors, is it still loading, it has results or was it the first search that was executed. Controller responsible for creating an instance of searchStatus is buildSearchStatus. It takes only one argument, that is, searchEngine.

import { buildSearchStatus, SearchEngine } from '@coveo/headless';

type SearchStatusProps = {
  searchEngine: SearchEngine;
};

export const builtSearchStatus = ({ searchEngine }: SearchStatusProps) =>
  buildSearchStatus(searchEngine);

Usage of the code above

Using below syntax one can create an instance of searchStatus. This can later be used to access search related information in code.

import { builtSearchStatus } from 'components/Foundation/Search/Coveo/SearchStatus';

const searchStatus = builtSearchStatus({ searchEngine: newSearchEngine });

Summary

In part 1 of this series, we have gone through the insights of the basic building blocks of Coveo headless search, what the basics controllers are, and what their purpose is. In part 2, we will be looking into how a query is created, what the basics are along with how to create an advanced query, and how to dispatch a query to be executed. We’ll also cover pagination in Coveo headless.


About the AuthorNisha Magnani
Nisha MagnaniSitecore Developer
Loading...