Querying Drupal Search API indexes using JSON:API
I'm happy to introduce a new module created in my research and development time at Centarro. The JSON:API Search API integration module enables decoupled Drupal architectures to use the Search API module to query indexed data. Before diving into the module, I want to explore the problem space it aims to solve.
Currently, consumers in a decoupled Drupal architecture would need to query the search index directly or access it through a proxy like ContentaJS. The problem with this approach is that your index query results match the data existing in your search index, not what is represented in Drupal. The GraphQL Search API module performs the same way – you're pushing your GraphQL query to Drupal, but it is returning the raw results from your search index. Querying directly from the search index means your consumer now must understand two different schemas or fetch the individual resources itself.
The Search API module works differently in a typical Drupal installation. It tracks entities that it may index and pushes data from those entities, as configured, into the search index. When you query a Search API index it retrieves the indexed data, but it doesn't just return those values. It uses the search query to retrieve the list of entity IDs that match the query, loads those entities, and returns them.
How the JSON:API Search API module works
The JSON:API Search API module brings this functionality as a JSON:API resource, so you can query your Search API indexes and receive data in a familiar way. The JSON:API index resource receives your desired filters, executes them as a Search API query, and then returns the resulting entity collection!
We will use the content and search index from the Commerce Demo module to showcase the module. The index is named Products and has a machine name identifier of products.
This index has various fields available. Some are set to fulltext so that they are searchable by keyword, and others are stored as raw values to allow more specific filtering. For instance, a product catalog might support both filtering by a specific product category term ID while enabling keyword search against product titles in that category.
With the JSON:API Search API module installed, we now query entities from our search index by using the resource at /jsonapi/index/products.
Here we can see the collection of products that are in the search index! This actually helps solve the same problem the JSON:API Cross Bundles module achieves - entity endpoints containing entities of different bundles.
Now, let's query our data. The index resource supports a special fulltext filter parameter. The parameter expects keywords that will be passed to the search index server. If we query the following URL, we will receive all products that contain the word "backpack" in their title:
We may also query on specific fields added to our Search API index configuration. The value of field_brand is indexed, allowing us to return a collection of products for a specific brand. The following URL will return products from the Happy Camper brand:
We can combine our filters to find "mugs" in the Happy Camper brand:
The module also supports pagination via JSON:API. Applying a limit to the query results via page[limit]=10 will return pagination links that populate the correct offset.
Footnotes and caveats
The JSON:API Search API module is still in early release, so here are a few notes for people that wanted to start working with the module today.
Filtering index fields
At the moment, the module isn't entirely compliant with the JSON:API spec for filter parameters. Filtering in JSON:API allows you to provide filter parameters in a verbose or shorthand format. The module currently only lets you filter using the shorthand notation, which is fine for most use cases.
The shorthand notation is a simple key/value pair that filters results using a specific field value. We can eventually provide more verbose filters that allow specifying the field, operator, and value like this:
&filter[last-name-filter][condition][path]=field_last_name &filter[last-name-filter][condition][operator]=STARTS_WITH &filter[last-name-filter][condition][value]=J
This isn't currently supported, but patches are welcome.
The Search API module indexes entity reference values via the entity's identifier, while Drupal's JSON:API implementation uses the entity UUID for retrieving individual entities. This may result in additional work if you are trying to build a link to a referenced piece of content from your index result's data.
Sorting with the sort parameter is not yet available.
Creating your own custom JSON:API resources
The JSON:API Search API module requires the JSON:API Resources module. The JSON:API Resources module is collaboratively developed between myself and Gabe Sullice of Acquia, one of the leads for the API-First Initiative. The module enables developers to write custom JSON:API resources for custom JSON:API integrations.