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-project
and 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.
Two important folder structures to be aware of:
/build
- This is what gets updated upon runningnpm run build
. It is what you will effectively be browsing when you runnpm 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.