/workshop-algolia-zenika

WorkShop introducing Algolia at Zenika Lille.

Primary LanguageHTMLApache License 2.0Apache-2.0

Workshop Algolia @Zenika

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.

Back-end (Step 1 and 2)

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.

Front-end (Step 3)

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 😉

Step 0: Get started

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!

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!)

Step 1: From JSON to Algolia

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);
    ```

Step 2: Customizing the index

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']);

    ```

Step 3: Integrate your search engine in a front-end

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!

Load instantsearch.js with your Algolia credentials

  • 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', }); ```
## Add your first widget: a [`searchBox`](https://community.algolia.com/instantsearch.js/documentation/#searchbox) for user input
  • In index.html, notice the <input id="searchbar" />: we'll use this input for our search
  • In index.js, uncomment the code that creates the searchBox 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 a hits widget and start the search
**Code** ```js search.addWidget( instantsearch.widgets.hits({ container: '#hits-container', hitsPerPage: 20, templates: { item: `{{title}}`, empty: `No results` } }) ); ```

Improve the search result display with a better template

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 by document.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' } ```

Enrich the data before displaying it

Transform your data to make it more useful for your users

  • In index.js, add to the hits widget a transformData function to enrich your search results: use the publishedDate timestamp to create a human-readable date, and create a new comments 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>

Help your users understand the search results with highlighting

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 the searchableAttributes in the results: simply replace {{attribute}} by {{{_highlightResult.attribute.value}}} for each searchable attribute
**Code** ```html <script id="templateSearch-hit" language="x-template">
{{{_highlightResult.title.value}}}

{{{_highlightResult.description.value}}}

</script> ```

Display statistics/metadata about your search

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 a stats widget:
**Code** ```js search.addWidget( instantsearch.widgets.stats({ container: '#stats-container', cssClasses: { root: 'search-stats' } }) ); ```

Add pagination to your interface

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 a pagination 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 }) ); ```

Let your users filter by tag

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 a refinementList 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' } }) ); ```

Reflect the state of the interface in the url

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 }); ```

Step 4: Going further

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:

Promote some articles over the other results

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 to true)

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 use promoted to sort results

Once the setting is updated, promoted results will always be displayed before other ones 👌

Restrict what your users can see

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 to promoted to remove this attribute from the engine's response

Control what your teammates can access

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 your dev_foo/dev_bar/... indices

[is-doc]: https://community.algolia.com/instantsearch.js/documentation/#widgets)