ElasticSearch + Ghost: Part III
This is the third part of the series explaining how to integrate Elastic Search with your self hosted Ghost blog. Make sure to have read parts one and two first!
Introduction
Up until the previous part, we’ve covered the basics of Elastic Search and we’ve modified the Ghost server to create an API that will return all of our posts in the JSON format. We’ve also created a script that will call this API and index all of the posts into Elastic Search.
In this part, we are going to create the API on the server that will execute the actual search. This API will receive the search query, proxy it through to Elastic Search and then send out the results.
To be able to see the results, we’ll add a simple page to Ghost that will just display the output of the query.
Creating the Search API
Let’s break it down:
- Create the Elastic Search query and test it out.
- Create the route for our API.
- Fill in the handler for out newly created route.
- Create the search results page.
- Test it all out.
Creating the Elastic Search Query
Elastic Search has an amazing Query DSL. For now, we’ll be using a wildcard query along with highlighting and suggestions and will be returning specific fields. I strongly recommend you go through the docs and try out the queries against your local installation of Elastic Search. I use the Chrome extension Postman for testing.
Let’s jump right in!
This is what the query looks like:
{
"fields": ["slug", "title", "tags", "updated_at"],
"query": {
"wildcard": {
"_all": {
"wildcard": "query_text*"
}
}
},
"highlight": {
"fields": {
"title": {},
"tags": {},
"content": {}
}
},
"suggest": {
"suggestions": {
"text": "query_text",
"term": {
"field": "_all",
"suggest_mode": "always"
}
}
}
}
Note: Replace query_text
with your query.
Go ahead, test it out via your REST client of choice! Here’s a screenshot of a search for ‘blog’:
And this is the suggestor in action! I searched with the text ‘blogg’
All done! Let’s move on!
Creating the Route
We’ve already created routes before, so this should be familiar. Open up core/server/routes/frontend.js
and add the following line to it:
server.get('/search/', frontend.search_results);
Creating the handler
Open up the file core/server/controllers/frontend.js
and create a method search_results
to test communication with Elastic Search. The method should go something like this:
"search_results": function (req, res, next) {
// Build up the search request
var request_data = {
"fields": ["slug", "title", "tags", "updated_at"],
"query": {
"wildcard": {
"_all": {
"wildcard": req.query.q + "*"
}
}
},
"highlight": {
"fields": {
"title": {},
"tags": {},
"content": {}
}
},
"suggest": {
"suggestions": {
"text": req.query.q,
"term": {
"field": "_all",
"suggest_mode": "always"
}
}
}
};
// create the elastic search request
request_data = JSON.stringify(request_data);
var esRequest = require('http').request({
host: 'localhost',
path: '/posts/_search',
port: 9200,
method: "POST",
headers: {
'Content-Type': 'application/json',
'Content-Length': request_data.length
}
}, function (esRes) {
var result = '';
esRes.on('data', function (chunk) {
result += chunk;
});
esRes.on('end', function () {
var response = JSON.parse(result);
// render the results
res.render('results', {
results: response,
resultsJSON: JSON.stringify(response, null, 2),
query: req.query.q
});
});
});
// search!
esRequest.write(request_data);
esRequest.end();
},
Here’s whats happening: we set up the page to work off a GET request to the URL /search/?q=[search_string]
. As you can see, there’s a call to results
. We’ll be creating this next.
Create the Test Page
Alright, we’re almost there. The simplest way to test out our search API would be to render the response from ElasticSearch on to an empty page.
Create results.hbs
in the folder /content/themes/[your theme]/
. Here, [your theme]
is your currently selected theme. Ghost comes with Casper as the default theme.
We are going to keep the contents of results.hbs as simple as possible, all we need is to be able to test out the search response.
{{!< default}}
<h1>{{query}}</h1>
<pre><code{{resultsJSON}}</code></pre>
the `` means that this partial inherits from default.hbs, so should will still look like a part of your site.
Alright, we’re done! Restart your server and you should be good to go!
Testing it all out!
Ok, we’re almost there. Testing it is easy: simply navigate to [your site url]/search/?q=blog
and you should be greeted with the search results!
Go ahead, clean up your results page!
Summary
We’ve finally managed to (in a somewhat hackish manner) integrate ElasticSearch into our Ghost blog!
Next Steps
Now that the basic integration is done I can think of quite a few changes that can/should be made to clean up the code and enhance the search feature:
- The bulk indexing of all posts should occur automatically whenever a post is created/modified/deleted.
- The
/search
handler in the controller should return JSON results when an AJAX call is made instead of a non-AJAX call. This feature could be extended to build up an auto-suggester and/or an entirely AJAX powered search. - Refactor the code in the frontend controller!! I do not think the Ghost creators of the platform will be pleased at the way the code is currently written!
- Open Search integration!
Thoughts? Rants? Advice?