Welcome to the Algolia Zenika Workshop! In a few steps, you'll see how to build your search applications with Algolia.
You will learn how to:
- Use Algolia in your back-end to index your data 🚀
- Configure the engine to search the way you like 🔎
- Build a front-end in a few minutes with instantsearch.js âš¡
We will use Smashing Magazine's website as an example project, and will go from raw JSON data to a fully functional search interface.
In Step 1 and Step 2, you will build the back-end. Documentation links will point you to what needs to be done, and you'll find code samples if you're stuck.
In Step 3, you will build the front-end from an existing webpage.
We provide you the HTML/CSS/JS code so you can simply follow this readme and uncomment the code blocks to get a functional front-end app. You are welcome to customize it further! Try tweaking the parameters and check [instantsearch.js' documentation][is-doc] to see what you could do 😉
Get the initial data and your API Credentials
For this workshop you need an Algolia account, the initial data and a front-end boilerplate. The two latter are in this repository's master branch!
- Clone the repository or download its archive
git clone git@github.com:PLNech/workshop-algolia-zenika.git
- Connect to your Algolia account and get your credentials:
- Your APPID
- Your Admin API Key (for creating/modifying/deleting data)
- Your Search-only API Key (for searching in your front-end)
In a real application you would avoid to use the Admin API Key (as it is allowed to do anything on your account) and would instead generate a specific api-key with write access, but for our workshop you can simply use the Admin API Key (as long as you don't publish your code on internet!)
Load the data and push it in an Algolia index
The first step is taking the JSON data dump, loading it in your code and pushing it to an Algolia index.
- Add algolia as a dependency
**Documentation** | *Getting started*
- [Python](https://www.algolia.com/doc/api-client/python/getting-started/#install)**Code samples**
- Python ```python
# requirements.txt
algoliasearch
```
- Java
```xml
<!-- pom.xml -->
<dependency>
<groupId>com.algolia</groupId>
<artifactId>algoliasearch</artifactId>
<version>[2,]</version>
</dependency>
```
- PHP
```php
composer require algolia/algoliasearch-client-php
```
- Take
articles.json
and load it in your code
**Documentation** | *Loading JSON*
- [Python](https://docs.python.org/3.6/library/json.html)**Code samples**
- Python ```python
with open("../data/articles.json") as f:
articles = json.load(f)
print(json.dumps(articles, indent=4))
```
- Java
```java
BufferedReader br = new BufferedReader(new FileReader("../data/articles.json"));
List<Articles> articles = com.algolia.search.Defaults.DEFAULT_OBJECT_MAPPER.readValue(br, Articles.class);
```
- PHP
```php
$batch = json_decode(file_get_contents('../data/articles.json'), true);
```
- With your credentials, initialize an API client
**Documentation** | *Initialize the API Client*
- [Python](https://www.algolia.com/doc/api-client/python/getting-started/#initialize-the-client)**Code samples**
- Python ```python
client = algoliasearch.Client("YOUR_APP_ID", "YOUR_ADMIN_API_KEY")
```
- Java
```java
APIClient client = new ApacheAPIClientBuilder("YOUR_APP_ID", "YOUR_ADMIN_API_KEY").build();
```
- PHP
```php
$client = new \AlgoliaSearch\Client("YOUR_APP_ID", "YOUR_ADMIN_API_KEY");
```
- Create your algolia index:
smashing
**Documentation** | *Initialize an index*
- [Python](https://www.algolia.com/doc/api-client/python/getting-started/#push-data)**Code samples**
- Python ```python
index = client.init_index("smashing")
```
- Java
```java
Index<Article> index = client.initIndex("smashing", Article.class);
```
- PHP
```php
$index = $client->initIndex('smashing');
```
- Add your objects to the index
**Documentation** | *Push data*
- [Python](https://www.algolia.com/doc/api-client/python/getting-started/#push-data)**Code samples**
- Python ```python
index.add_objects(articles)
```
- Java
```java
index.addObjects(articles);
```
- PHP
```php
$index->addObjects($batch);
```
Set the appropriate settings for your data: where to search, how to sort, how to filter
A search engine can be configured for so many use-cases that you need to customize its default settings if you want to bring as much value as possible to your users.
We'll configure two very important settings: how the engine will search in your data, and how the results will be sorted.
The former is controlled by searchableAttributes
, which lists the attributes of your records that could contain search terms.
The latter depends on customRanking
, which describes the attributes to use for sorting.
As an example, imagine we have searchableAttributes=['title']
and customRanking=['author', 'publishedDate']
: if the user searches for javascript
and two articles match in their title
, we would display them by alphabetical order of author name (and if they have the same author, they will be ordered by increasing publication dates).
- Set searchableAttributes (what can be searched) and customRanking (how results should be sorted)
**Documentation** | *Set settings*
- [Python](https://www.algolia.com/doc/api-client/python/settings/#set-settings)**Code samples**
- Python ```python
index.set_settings({
"searchableAttributes": ["title", "description", "tags", "author"],
"customRanking": ["desc(commentCount)"]
})
```
- Java
```java
index.setSettings(new IndexSettings()
.setSearchableAttributes(Arrays.asList("title", "description", "tags", "author"))
.setCustomRanking(Arrays.asList("desc(commentCount)")));
```
- PHP
```php
$index->setSettings(array(
"searchableAttributes" => array("title", "description", "tags", "author"),
"customRanking" => array("desc(commentCount)")));
```
- Set attributesForFaceting (which attributes can be filtered on) and confirm the new value
**Documentation** | *Get settings*
- [Python](https://www.algolia.com/doc/api-client/python/settings/#get-settings)**Code samples**
- Python ```python
res = index.set_settings({
"attributesForFaceting": ["tags.name"]
})
index.wait_task(res['taskID']) # as operations are asynchronous, we explicitly wait for task completion
print("Attributes for faceting: %s." % index.get_settings()['attributesForFaceting'])
```
- Java
```java
index.setSettings(new IndexSettings().setAttributesForFaceting(Arrays.asList("tags.name")))
.waitForCompletion(); // as operations are asynchronous, we explicitly wait for task completion
System.out.println(index.getSettings().getAttributesForFaceting());
```
- PHP
```php
$res = $index->setSettings(array("attributesForFaceting" => array("tags.name")));
$index->waitTask($res['taskID']); // as operations are asynchronous, we explicitly wait for task completion
$settings = $index->getSettings();
var_dump($settings['attributesForFaceting']);
```
Build a search interface quickly with instantsearch.js
For this step, you will need:
- an editor for modifying HTML/JS files (any editor is fine)
- a browser to display the page (either by manually opening the html or using
cd frontend; npm install && npm run serve
)
Don't hesitate to open the [instantsearch.js documentation][is-doc] : you can do a lot more with each widget than what is shown here!
- In
index.js
, uncomment the first code block and replace the placeholders with your credentials
**Code**
```js var search = instantsearch({ appId: 'YOUR_APP_ID', apiKey: 'YOUR_SEARCH_API_KEY', indexName: 'smashing', }); ```- In
index.html
, notice the<input id="searchbar" />
: we'll use this input for our search - In
index.js
, uncomment the code that creates thesearchBox
widget
**Code**
```js search.addWidget( instantsearch.widgets.searchBox({ container: '#searchbar', placeholder: 'Search for articles', autofocus: true, poweredBy: true }) ); ```Add a second widget to display search hits
- In
index.js
, uncomment the next code block to add ahits
widget and start the search
**Code**
```js search.addWidget( instantsearch.widgets.hits({ container: '#hits-container', hitsPerPage: 20, templates: { item: `{{title}}`, empty: `No results` } }) ); ```Use a HTML template for displaying the hits and some css to make it pretty
- In
index.html
, notice the<script id="templateSearch-hit" language="x-template">
node: we will use this template to enrich the display of our search results - In
index.js
, replace the item's template bydocument.getElementById("templateSearch-hit").innerHTML
**Code**
```js templates: { item: document.getElementById("templateSearch-hit").innerHTML, empty: `No results` } ```- In
index.css
, notice the.search-hits
class: we'll use it for styling our search results - In
index.js
, add to the hits widget a cssClasses property :cssClasses: {root: 'search-hits'}
**Code**
```js //templates: {...}, cssClasses: { root: 'search-hits' } ```Transform your data to make it more useful for your users
- In
index.js
, add to the hits widget atransformData
function to enrich your search results: use thepublishedDate
timestamp to create a human-readabledate
, and create a newcomments
attribute to pretty print the number of comments
**Code**
```js //cssClasses: {...}, transformData: { item: (hit) => { // Date in human-readable format hit.date = moment.unix(hit.publishedDate).format('MMMM Do, YYYY');// Number of comments
if (hit.commentCount > 1) {
hit.comments = `${hit.commentCount} Comments`;
} else {
hit.comments = hit.commentCout === 0 ? null : '1 Comment';
}
return hit;
} }
</details>
- In `index.html`, edit the template to use your new attributes (replace `commentCount` by `comments` and `publishedDate` by `date`)
<details>
<summary>**Code**</summary>
```html
<script id="templateSearch-hit" language="x-template">
<div class="search-hit">
<a href="{{url}}" class="search-hit--figure">
<img src="{{image}}" class="search-hit--image" />
</a>
<div class="search-hit--details">
<a href="{{url}}" class="search-hit--title">{{title}}</a>
<ul class="search-hit--infos pmd">
<li class="search-hit--info search-hit--info__date date rd">{{date}}</li>
<li class="search-hit--info search-hit--info__author">By <a href="{{authorUrl}}">{{author}}</a></li>
<li class="search-hit--info search-hit--info__tags tags">
{{#tags}}<a href="https://www.smashingmagazine.com/tag/{{slug}}/">{{{name}}}</a>{{/tags}}
</li>
<li class="search-hit--info search-hit--info__comments comments">{{comments}}</li>
</ul>
<p class="search-hit--excerpt">{{description}}</p>
</div>
</div>
</script>
Highlight the query terms in your search results to explain them to the user
Search result highlighting is one of the key components of a great search experience. Your search results already include highlighted attributes, you just need to use them in your results template!
- In
index.html
, edit the template to highlight thesearchableAttributes
in the results: simply replace{{attribute}}
by{{{_highlightResult.attribute.value}}}
for each searchable attribute
**Code**
```html <script id="templateSearch-hit" language="x-template">- {{date}}
- By {{{_highlightResult.author.value}}}
- {{#tags}}{{{name}}}{{/tags}}
- {{comments}}
{{{_highlightResult.description.value}}}
Use the
stats
widget to display contextual information
- In
index.html
, notice the<div id="stats-container">"
which will host your stats - In
index.js
, uncomment the next code block to add astats
widget:
**Code**
```js search.addWidget( instantsearch.widgets.stats({ container: '#stats-container', cssClasses: { root: 'search-stats' } }) ); ```Use the
pagination
widget to let your user navigate through pages of results
- In
index.html
, notice the<div id="pagination-container">"
which will host the pagination - In
index.js
, uncomment the next code block to add apagination
widget:
**Code**
```js search.addWidget( instantsearch.widgets.pagination({ container: '#pagination-container', maxPages: 20, // default is to scroll to 'body', here we disable this behavior scrollTo: false }) ); ```Use the
refinementList
widget to display an interactive tag cloud
- In
index.html
, notice the<div id="tags-container">"
which will host your tags - In
index.js
, uncomment the next code block to add arefinementList
widget:
**Code**
```js search.addWidget( instantsearch.widgets.refinementList({ container: '#tags-container', attributeName: 'tags.name', operator: 'and', limit: 10, cssClasses: { root: 'search-tags', header: 'search-tags-header' }, templates: { header: 'Tags' } }) ); ```Make it possible to share a link to search results with
urlSync
- In the instanciation of instantsearch.js, add the
urlSync
attribute:
**Code**
```js var search = instantsearch({ appId: 'YOUR_APP_ID', apiKey: 'YOUR_SEARCH_API_KEY', indexName: 'smashing', urlSync: true }); ```Congratulations for reaching this far! You just built a fully functional search interface, from the data loading in your back-end to the search display in your front-end 💪
Here are a few ideas of what else you could do with your Algolia application:
Let's update our initial data: each article will now have a promoted
attribute, and whentrue
we will display this article before the other results.
- Update your articles to add a
promoted
boolean attribute to each article (and randomly set some totrue
)
When you save an edited object, the engine will automatically index again your data. The update and reindexation is atomic: your front-end will search in the previous data until the new index is available, at which point it automatically targets the new data.
- Update your
customRanking
setting to first usepromoted
to sort results
Once the setting is updated, promoted results will always be displayed before other ones 👌
You may want to hide that some articles are promoted over the other ones. This can be done with the unretrievableAttributes
setting, which lets you specify attributes that won't be returned in the search results (so they will only be used for ranking them).
- Set the
unretrievableAttributes
setting topromoted
to remove this attribute from the engine's response
You just welcomed an intern at your company, which will be doing some development on your application. But you don't want him to mess with your production data... What can you do?
-
Create a new collaborator in the Team tab
-
Choose what features he has access to: by default he will only see the dashboard and search data, but for example cannot change settings or see your analytics
-
Click edit restrictions to limit which indices he can access: for example you can use
dev_*
to only give him rights to yourdev_foo
/dev_bar
/... indices
[is-doc]: https://community.algolia.com/instantsearch.js/documentation/#widgets)