Lucene, GraphQL and Orchard Core

A brief guide on implementing Search using Orchard Core, Lucene and GraphQL.

Sipke Schoorstra
The Startup

--

Recently I was asked how one would go about enabling search functionality in a front-end single-page-application (SPA) that is powered by Orchard Core CMS.

Now, I already knew that Orchard provides search functionality using Lucene.NET, so I figured its Lucene module probably exposes a REST API that the SPA should be able to call. Sure enough, the documentation confirmed this.

What I didn’t know however, is that the Lucene module integrates with GraphQL, effectively allowing one to query Lucene indices using GraphQL.

Say what?

Yeah that’s right. We can query Lucene indices using GraphQL, right out of the box. GraphQL offers great flexibility when it comes to querying any number of data sources. But the consumer needn’t know nor care about any of that. It only specifies what type it wants to query, and what fields it needs back from it. Be it content items, other APIs, or Lucene indices. All uniformly available through a consistent API with GraphQL.

Let’s see how this works!

Enabling Lucene

The first thing to do before we can query anything, is to make sure that the Lucene feature is enabled. Depending on the recipe you selected during setup, this may or may not be the case already. If you selected the Blog recipe, then Lucene will be enabled for you.

Select the Blog recipe and your site will have Lucene enabled by default.

If you’re not sure if Lucene is enabled, go to the Admin Dashboard, then select Configuration → Features from the left menu, and enable the feature called Lucene if it’s disabled.

Lucene is enabled.

Setting up a Lucene Query

To demonstrate how you can query Lucene indices, we’ll create a new Query object using the Admin Dashboard that returns all blog posts containing a user-provider search term.

On the left menu, select Search → Queries → All Queries, then select the Add Query button.

Select Add Query

A modal dialog appears, showing all available Query Types. Orchard Core being a modular, extensible framework, might have any number of modules installed that adds to the types of queries we can use. We are just interested in creating a Lucene query, so select that one.

Select the Lucene query type

On the Lucene query editor screen, enter the following details:

  • Name: BlogPostsQuery
  • Schema:
    {
    "type": "ContentItem/BlogPost"
    }
  • Index: Search
  • Return Content Items: checked
  • Query:
{
"query": {
"bool": {
"must": [
{
"match": {
"Content.ContentItem.FullText": "{{ Term }}"
}
},
{
"term": {
"Content.ContentItem.ContentType": "BlogPost"
}
}
]
}
}
}

The above query is a liquified (templated using Liquid syntax) Lucene query that finds all content items that:

  1. Have a field called Content.ContentItem.FullText containing whatever string value returned by the Liquid variable Term (we’ll see how to provide a value for that shortly).
  2. Have term field called Content.ContentItem.ContentType having the exact value of BlogPost.

This query effectively yields all content items of type BlogPost containing a yet-to-be-defined search term that we need to specify as a parameter.

Make sure to select the Save button before we try out our query.

Testing the Lucene Query

From the left menu, select Search → Queries → All Queries. You’ll see the query we just created, and 3 buttons called Run, Edit and Delete. Select the Run button.

Select the Run button on the newly created BlogPostsQuery.

On the next screen, we see our query template, as well as a Parameters field. That’s where we’ll supply the Term parameter that we defined in the query template. As an example, we’ll try looking for all blog posts containing the word “explore” (which should yield exactly 1 result if you used the Blog recipe).

Running the query with “explore” as the Term value yields exactly one matching document.

Now we’ve seen that that works, we’re ready to try out the query using an API call from Postman. After that, we’ll do the same, but using a GraphQL query API call.

Lucene + GraphQL

Finally, it’s time to query the Lucene index using GraphQL, which enables us to specify exactly what fields to be included in the response.

First, we’ll need to enable the GraphQL feature:

Enable the GraphQL feature.

Before we see how to invoke the GraphQL endpoint using a HTTP request, let’s first try it out using GraphiQL. From the left menu, select Configuration → GraphiQL.

Notice that the Explorer view contains our BlogPostsQuery query:

The GraphiQL explorer shows our BlogPostsQuery as a Type.

Go ahead, and select/enter the following GraphQL query:

query MyQuery($parameters: String!) {
blogPostsQuery(parameters: $parameters) {
markdownBody {
html
}
subtitle
author
displayText
}
}

Notice that I’m passing in a GraphQL variable named $parameters, so make sure to define that variable in the Query Variables view:

{
"parameters": "{\"Term\": \"explore\"}"
}

When selecting the Play button, you should receive the following response:

{
"data": {
"blogPostsQuery": [
{
"markdownBody": {
"html": "<p>Never in all their history have men been able truly to conceive of the world as one: a single sphere, a globe, having the qualities of a globe, a round earth in which all the directions eventually meet, in which there is no center because every point, or none, is center — an equal earth which all men occupy as equals. The airman's earth, if free men make it, will be truly round: a globe in practice, not in theory.</p>\n<p>Science cuts two ways, of course; its products can be used for both good and evil. But there's no turning back from science. The early warnings about technological dangers also come from science.</p>\n<p>What was most significant about the lunar voyage was not that man set foot on the Moon but that they set eye on the earth.</p>\n<p>A Chinese tale tells of some men sent to harm a young girl who, upon seeing her beauty, become her protectors rather than her violators. That's how I felt seeing the Earth for the first time. I could not help but love and cherish her.</p>\n<p>For those who have seen the Earth from space, and for the hundreds and perhaps thousands more who will, the experience most certainly changes your perspective. The things that we share in our world are far more valuable than those which divide us.</p>\n"
},
"subtitle": "Problems look mighty small from 150 miles up",
"author": "admin",
"displayText": "Man must explore, and this is exploration at its greatest"
}
]
}
}
GraphiQL in action.

Now let’s execute a GraphQL query using Postman.

Similar to what we did with the other two APIs, we first need to grant the Anonymous user role the necessary permission. In this case, the Execute GraphQL permission:

Grant the Anonymous role the Execute GraphQL permission.

Now we can fire a GraphQL request using Postman:

POST /api/graphql HTTP/1.1
Host: localhost:8196
Content-Type: application/graphql
query {
blogPostsQuery(parameters: "{ \"Term\":\"explore\" }") {
displayText
markdownBody {
html
}
}
}

Which results in the following response:

{
"data": {
"blogPostsQuery": [
{
"displayText": "Man must explore, and this is exploration at its greatest",
"markdownBody": {
"html": "<p>Never in all their history have men been able truly to conceive of the world as one: a single sphere, a globe, having the qualities of a globe, a round earth in which all the directions eventually meet, in which there is no center because every point, or none, is center — an equal earth which all men occupy as equals. The airman's earth, if free men make it, will be truly round: a globe in practice, not in theory.</p>\n<p>Science cuts two ways, of course; its products can be used for both good and evil. But there's no turning back from science. The early warnings about technological dangers also come from science.</p>\n<p>What was most significant about the lunar voyage was not that man set foot on the Moon but that they set eye on the earth.</p>\n<p>A Chinese tale tells of some men sent to harm a young girl who, upon seeing her beauty, become her protectors rather than her violators. That's how I felt seeing the Earth for the first time. I could not help but love and cherish her.</p>\n<p>For those who have seen the Earth from space, and for the hundreds and perhaps thousands more who will, the experience most certainly changes your perspective. The things that we share in our world are far more valuable than those which divide us.</p>\n"
}
}
]
}
}

Summary

We’ve seen how easy it is to enable search functionality and consume the available APIs from Postman using Lucene and GraphQL, allowing us to use a consistent API from our applications.

And the best part? We get all of these features out of the box. No coding required, just a bit of configuration, making your site’s content searchable.

--

--