Insights

Setting Up Coveo Headless

Let's Revisit Coveo Headless

It's been a solid 6 months since we posted here about Coveo's Headless product. And wow has it matured in that timeframe. It's now at version 1.41.7 and in my recent refresher oh so smooth it was getting it all set up. I vaguely remember having some challenges initially. And let me tell you, none of what I encountered was present this time around.

So let's have a look, shall we.

The Setup

So before we dive right in and install @coveo/headless, let's do some pre-work. Something that I think would have helped us a bit more the first time around.

Install NVM

Think of NVM as a version manager for Node. In the past, you might have only had a single version of Node on your machine at any given time. With NVM you can have multiple versions loaded and select which one you need for whatever project you are working on. You can download NVM for Mac / Linux or download for Windows.

Given Headless requires a Node.js version of 12 or greater, you can first check which version you're running and if need be, using NVM, load a newer version.

Get Git

Yes, you will want to have Git as, a) it's just good practice and b) it could save you time in the end when building components that you can simply and easily branch off and not have it interfere with your core application. Get Git here.

Install TypeScript

Headless is written in TypeScript, as such, when we create a project for Headless, it's just recommended to use TypeScript. Install it on your system by loading up a Command Prompt and typing:

npm install -g typescript

Create A Project

One thing I've learned in my short time of building with Headless is that this next step is pretty darn important. So let's set one up right now. Find a location on your computer that you'd like to run the project from, and once you have a name in mind (e.g. headless-project), type the following:

npx create-react-app --template typescript headless-project

Yes, for the purposes of this article, we are using React. You could use Vue or Angular if so desired. The important part, is the --template typescript portion as this will ensure the project is properly supporting TypeScript.

Install @coveo/headless

You've made it this far and now is the time, let's install @covoe/headless. Move into your project, cd headless-projectand then type the following:

npm install @coveo/headless

How's It Looking?

Let's have a look at what our project looks like right now.

A headless project code example

Two important folder structures to be aware of:

  • /build - This is what gets updated upon running npm run build. It is what you will effectively be browsing when you run npm start
  • /src - Where the magic is constructed. Right now, it's pretty bare-bones, but we will flesh it out here in a few moments.

Getting Started

Now we're going to run through some core concepts involved when building a Headless project.

Headless Engine

This is the piece that basically gives our project juice. As it exists, it holds the entire Headless state for the search interface.

As of version 1.41.7, there are four different types of engines within Headless. They are as follows:

  • Search
  • Recommendation
  • Product Recommendation
  • Product Listing

For the purposes of this article, we're going to focus on the Search Engine but in the future, we will look to cover others.

Let's build a basic engine, shall we? Create a new file and call it Engine.tsx in the root. Fill it with the following (updating with your own organizationId and accessToken from the Coveo Platform).

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

export const headlessEngine = buildSearchEngine({
configuration: {
    accessToken:'xxxxxxxxx-x-xxxx-xxxx-xxxxxxxxx', 
    organizationId: 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' 
}});

That's it, for now at least. With this bit, we can now proceed with the initial setup of the App.tsx file to manage the search interface and execute searches.

App.tsx

Within the App.tsx at the top of the file, your import statements will need to be updated to reflect the following:

import React from 'react';
import {useEffect} from 'react'; // Needed to run the engine

//Coveo Headless
import {loadSearchAnalyticsActions, loadSearchActions} from '@coveo/headless';
import {headlessEngine} from './Engine';

Then within the App() function, we're going to load in the engine.

function App() {
    useEffect(() => {
        const {logInterfaceLoad} = loadSearchAnalyticsActions(headlessEngine);
        const {executeSearch} = loadSearchActions(headlessEngine);
        headlessEngine.dispatch(executeSearch(logInterfaceLoad()));
    });
    return (
        <div className="App">
            <header className="App-header">
                <h1>Coveo Headless Search Interface</h1>
            </header>
            <main>
            </main>
        </div>
    );
  }

Components & Controllers

When it comes to Headless components, they are organized with a corresponding component and its accompanying controller. The controller is the primary way to interact with the engine's state.

It's best to learn by example, so let's set this up.

Adding A Search Box

The Component

First, let's create our component. Create a directory called components. Inside that folder, create a file called search-box.tsx. The code you will be inserting, which is a good starting point, is as follows. The original source code, and potentially improved code, can be found here.

import {SearchBox as HeadlessSearchBox} from '@coveo/headless';
import {FunctionComponent, useEffect, useState} from 'react';

interface SearchBoxProps {
    controller: HeadlessSearchBox;
}

export const SearchBox: FunctionComponent<SearchBoxProps> = (props) => {
    const {controller} = props;
    const [state, setState] = useState(controller.state);
    const [focused, setFocused] = useState(false);

    useEffect(() => controller.subscribe(() => setState(controller.state)), [
    controller,
    ]);

    // style used within the search box
    const suggestionStyle = {
    cursor: 'pointer',
    };

    // What is returned when the component is called
    return (
    <div className="search-box">
        <input
        value={state.value}
        onChange={(e) => controller.updateText(e.target.value)}
        onKeyDown={(e) => {
            if (e.key === 'Enter') {
            controller.submit();
            } else if (e.key === 'Escape') {
            controller.clear();
            (e.target as HTMLInputElement).blur();
            }
        }}
        onFocus={() => setFocused(true)}
        onBlur={() => setFocused(false)}
        />
        <button onClick={() => controller.submit()}>Search</button>
        <button onClick={() => controller.clear()}>Clear</button>
        {focused && state.suggestions.length > 0 && (
        <ul>
            {state.suggestions.map((suggestion) => {
            return (
                <li
                style={suggestionStyle}
                key={suggestion.rawValue}
                onMouseDown={(e) => e.preventDefault()}
                onClick={() => controller.selectSuggestion(suggestion.rawValue)}
                dangerouslySetInnerHTML={{__html: suggestion.highlightedValue}}
                ></li>
            );
            })}
        </ul>
        )}
    </div>
    );
};

The Controller

The nice thing is, when it comes to controllers, think of these as the options that can be configured within the component. In the case of the Search Box. You have options such as highlighting.

import {
buildSearchBox,

} from '@coveo/headless';
import {headlessEngine} from '../engine';

export const searchBox = buildSearchBox(headlessEngine, {
options: {
    highlightOptions: {
        notMatchDelimiters: {
            open: '<strong>',
            close: '</strong>',
        },
        correctionDelimiters: {
            open: '<i>',
            close: '</i>',
        },
    },
},
});

Adding It To The App

Once you have the component (and if needed, the controller) in place it's time to add it to the App. So open up your App.tsx file and start by adding the necessary import statements.

import {SearchBox} from './components/search-box';
import {
    searchBox,
} from './controllers/controllers';

With that in place, let's add the component to the app body.

<div className="App">
    <header className="App-header">
        <h1>Coveo Headless Search Interface</h1>
    </header>
    <main className="App-body">
        <div className="search-section">
        <SearchBox controller={searchBox} />
        </div>
    </main>
</div>

Once saved, run the following to compile the TypeScript and run the app.

npm run build
npm run start

Voila! This is just the start. Obviously not much happens with the Search Box so, in our next article, we will look into the Result List.

We'll be cool about it. Promise.

Make every experience personal with a predictive, intelligent search and relevance solution.

Meet David Austin

Development Team Lead

📷🕹️👪

David is a decorated Development Team Lead with Sitecore Technology MVP and Coveo MVP awards, as well as Sitecore CDP & Personalize Certified. He's worked in IT for 25 years; everything ranging from Developer to Business Analyst to Group Lead helping manage everything from Intranet and Internet sites to facility management and application support. David is a dedicated family man who loves to spend time with his girls. He's also an avid photographer and loves to explore new places.

Connect with David