Colorfield logo

Drupalicious

Published on

Drupal faceted search with Typesense and InstantSearch

Authors
Typesense search results with InstantSearch

In this post, we will show how to setup a decoupled Algolia InstantSearch implementation with a Typesense backend and Drupal Search API.

Typesense is an open source alternative to Algolia, the first difference that I noticed is the pricing model:

So I started to evaluate it quickly on my local with the homebrew package and it's blazing fast!
It's written in C++ and stores in the memory. The main difference with Drupal facets is that Drupal / Views is fully skipped, the query is done straight on the server.

I was looking for an alternative to Drupal facets for several reasons

  • A project we are working on will be fully decoupled soon and I wanted to incrementally decouple by soft decoupling faceted search that were built with views
  • We had a performance issue on high traffic sites, as ajax updates facets blocks with POST requests, which caused issues with CDN cacheability
  • Our Solr setup is quite complex
  • Some ajax cases were not working out for us without patches, that we need to maintain when updating

Typesense appeared to be a very good candidate and it can also be extended for semantic search / RAG with the AI module. When talking with others at the Moutain Camp, Meilisearch was also mentioned as a nice alternative. The focus of this post is Typesense though, so we will continue with the latter.

Fortunately the heavy lifting was already done

The frontend example below will use React, but it can be easily ported to your favourite stack.

Step by step configuration

Install and start Typesense server

We will use macOS here

brew install typesense/tap/typesense-server@28.0
brew services start typesense-server@28.0

For other infra, follow this documentation Local Machine / Self-Hosting

The default API key is xyz

Install Drupal with Search API Typesense

For our example, we will use what Drupal has out of the box: the Article content type and the Tags vocabulary.

  • Articles will be Algolia InstantSearch Hits
  • Tags will be the Facet items, in our example we will use the RefinementList
# Install Drupal and Drush
composer create-project drupal/recommended-project drupal-typesense
cd drupal-typesense
php web/core/scripts/drupal install standard
composer require drush/drush

## Generate some tags and articles
composer require drupal/devel
drush en devel_generate
drush gent 10 --bundles=tags --max-depth=1
drush genc --bundles=article

# Install Typesense
composer require 'drupal/search_api_typesense:^1.0'
drush en search_api_typesense

Create the Search API server

  • Head to /admin/config/search/search-api
  • Add the Server
    • Name: Typesense
    • Admin API Key: xyz
    • Host: localhost
    • Port: 8108
    • Protocol: http
  • With the API Keys tab, create a readonly one
    • Description: Search articles
    • Actions: tick documents:search
    • Collections: articles (that matches the index machine name)
  • Copy the API Key for later

Create the index

  • Head back to /admin/config/search/search-api
  • Add the index
    • Name: Articles
    • Datasources: Content
    • Content datasource: Only those selected and select Article
    • Server: Typesense
    • Click on Save and add fields
    • Expand field_tags and add field_tags:entity:name
    • Add title
    • Click on Done

Configure fields

  • Change the machine name name for the Tags to tags and select Typesense: string[] for the type
  • Select Typesense: string for the title
  • Click on Save changes

Configure Typesense schema

  • Use the Schema tab, so /admin/config/search/search-api/index/articles/schema
  • For tags, tick Facet and Optional
  • Click on Save
  • Click on Index now

Configure processors

You might most likely want to enable at least Content access and/or Entity status.

You are all set for the Drupal part.

For more details, follow instructions from the maintainers.

Create the React UI

We will use Vite React / SWC / Typescript template with PNPM, but feel free to use anything else.

pnpm create vite facets --template react-swc-ts
cd facets

Add Algolia packages

pnpm add instantsearch.js
pnpm add react-instantsearch

Add Typesense adapter

pnpm add typesense-instantsearch-adapter

Minimal application

In App.tsx, replace the boilerplate inner code in the App function, your file should look like this

const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    // Be sure to use the search-only-api-key.
    // Use env variables.
    apiKey: 'READ_ONLY_API_KEY',
    nodes: [
      {
        host: 'localhost',
        port: 8108,
        protocol: 'http',
      },
    ],
  },
  additionalSearchParameters: {
    query_by: 'title',
  },
})

const { searchClient } = typesenseInstantsearchAdapter
const indexName = 'articles'

return (
  <div>
    <header className="header">
      <h1 className="header-title">
        <a href="/">React InstantSearch with Typesense</a>
      </h1>
      using Drupal Search API
    </header>

    <div className="container">
      <InstantSearch searchClient={searchClient} indexName={indexName} routing={true}>
        <Configure hitsPerPage={10} />
        <div className="search-panel">
          <div className="search-panel__filters">
            <FacetPanel header="Tags">
              <RefinementList attribute="tags" sortBy={['name:asc']} />
            </FacetPanel>
          </div>

          <div className="search-panel__results">
            <SearchBox placeholder="" className="searchbox" />
            <Hits hitComponent={Hit} />
            <div className="pagination">
              <Pagination />
            </div>
          </div>
        </div>
      </InstantSearch>
    </div>
  </div>
)

The complete example for the frontend is available in this repository, just make sure to replace the api key by the read-only one set earlier.

What's next?

You can then embed this as a React island with a Drupal library, for soft decoupling, or use it in your fully decoupled project.

In another post, I will elaborate some of the findings that I had to solve, for the frontend to be production ready, like:

  • handle multilingual facets
  • filter content by language
  • write a custom processor for language fallback
  • sort facet items by term weight
  • use path alias
  • use radio widgets
  • use an empty state
  • host the Typesense server with Docker
  • ...