Guides / Solutions / Gallery

Federated Search

Federated search is a popular search and discovery pattern that displays relevant results from multiple data sources on the same page. Ideally, the page uses different layouts to best present each type of content. The benefit from a user’s perspective is that a single query multiplies their options. This encourages them to browse and discover more of your online business.

Check out our series of articles that showcase the business value of federated search. The series starts with: What is federated search?. Please refer to our Live Demo for a complete sample implementation.

Requirements

Difficulty
Intermediate
Prerequisites Instant Search 4+

Implementation guide

Any business in any industry can leverage federated search. For example, an online journal can divide news into different categories (a single screen with international, sports, and editorials), and a SaaS company can divide its online services into apps, platforms, and articles or FAQs.

In this guide, we’ll use an electronics webshop as an example for federated search.

Screenshot of the federated search demo

Using one or more indices

Many implementations of federated search use multiple indices for their search. This is because different content types normally require different data structures, and therefore different indices. For example, products and product reviews require two indices: a product index and a separate reviews index.

However, federated search does not require you to use multiple indices. Even though a federated search displays multiple results, you can filter (or contextualize) a single index to create these different result sets. For example, you can query a single index that contains both movies and books twice, once with a filter on books, and once with a filter on movies.

Here is the most common flow of information:

Flowchart: From Data to Index to UI/UX.

  1. You have multiple data sources. For each data source, you create an index.
  2. Federated search will search in each index, or in a single index with different filters.
  3. The search interface uses a single search bar, and displays the different type of results in different ways in the same interface.

File and Index Structure

For this implementation, we’ll need three files:

  • index.html
  • src/style.css
  • src/main.js

You will also need two Algolia indices:

High-level overview

To create a federated search, we need to do the following:

  • Display a single search bar and send the query to Algolia with every keystroke, like a regular search interface.

  • Send the query to Algolia for each type of content, simultaneously. In our example, we will make two simultaneous search queries.

  • Receive the response for each query and display the results of each response. In our example, in the first two columns.

  • As part of the search in the main index, we receive the facets within the response. We will display them in our third column.

The HTML - creating a combined layout

Our federated search experience will have three columns. The first column displays the suggestions from the Query Suggestions index; the second column presents the products from the main index; and the third displays the facets (refinements).

The display logic is as follows:

  • Place a single container with the class federated-results-container inside the search bar widget container. This helps display the results right underneath the search bar.

  • Place three containers inside federated-results-container (one for each column):

    • A column with the ID suggestions. This will hold the Query Suggestions.
    • A column with the ID products. This will hold the products.
    • A column with the ID categories. This will hold our facets.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="searchWidget widget">
  <!-- any additional content you want to display -->
  <div class="federated-results-container">
    <div class="column">
      <h2>Suggestions</h2>
      <div id="suggestions"></div>
    </div>
    <div class="column">
      <h2>Products</h2>
      <div id="products"></div>
      <a href="#">See more products</a>
    </div>
    <div class="column">
      <h2>Categories</h2>
      <div id="categories"></div>
    </div>
  </div>
  <!-- any additional content you want to display -->
</div>

The CSS - creating a column layout

The layout is central to a federated search interface. We use CSS to create unique layouts for each result set.

So far, we used the HTML to divide up the screen into three parts. Now, we’ll use the CSS to turn those elements into three columns. For this, we use the classes .federated-results-container and .federated-results-container .column.

Here is a skeleton view of your CSS file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
 FEDERATED SEARCH BOX STYLE
*/
 
.federated-results-container {
  width: 150%;
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  background: linear-gradient(
      -180deg,
      hsla(0, 0%, 100%, 0),
      hsla(0, 0%, 100%, 0.8)
    )
    #ebecf3;
  box-shadow: 0 20px 44px 0 rgba(0, 0, 0, 0.2);
  border-radius: 8px;
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  display: none;
}

@media (max-width: 1130px) {
  .federated-results-container {
    width: 100vw;
  }
}

.federated-results-container .column {
  text-align: left;
  font-weight: normal;
  position: relative;
  padding: 12px 16px;
}

.federated-results-container .column:not(:last-child)::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  width: 1px;
  height: 100%;
  background-color: #aaa;
  opacity: 0.5;
}

.federated-results-container .column h2 {
  font-weight: normal;
  font-size: 24px;
  margin-bottom: 16px;
}
 

The JavaScript - making the search interactive

For our federated search, we use the index widget to query a specific index, and add additional widgets for our configuration and templates.

The first step is to create an InstantSearch instance, and add all of its relevant widgets. We’ll assign the InstantSearch instance to a variable search. The widgets we’ll add are the searchBox widget, two index widgets, and a federatedRefinement widget which we’ll create from scratch.

Here’s a skeleton view of what we’re trying to do:

1
2
3
4
5
6
search.addWidgets([
  searchBox({ /* search box */}),
  index({ /* products */ }),
  index({ /* suggestions */ }),
  federatedRefinement({ /* categories */ }),
]);

With the skeleton in place, we’ll create the searchBox widget and link it to the container with the search-box ID.

1
2
3
4
5
6
7
8
9
10
11
12
search.addWidgets([
  searchBox({
    container: '#search-box',
    placeholder: 'Search for products',
    showReset: true,
    showSubmit: true,
    showLoadingIndicator: true,
  }),
  index({ /* products */ }),
  index({ /* suggestions */ }),
  federatedRefinement({ /* categories */ }),
]);

Display and format the main results

After that, we’ll configure the first index widget. This is the widget that contains our products. It targets our main index, instant_search. We have to add the hits widget to this index widget, so that it knows how to display our results. We tell it how to render results by providing the templates property on the hits widget.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
search.addWidgets([
  searchBox({ /* search box */ }),
  index({
    indexName: 'instant_search',
    indexId: 'products',
  }).addWidgets([
    configure({
      hitsPerPage: 3,
    }),
    hits({
      container: '#products',
      templates: {
        empty: 'No results',
        item: `
              <div class="item">
                  <figure class="hit-image-container"><div class="hit-image-container-box"><img class="hit-image" src="{{image}}" alt=""></div></figure>
                  <p class="hit-category">&#8203;​</p>
                  <div class="item-content">
                      <p class="brand hit-tag">{{{_highlightResult.brand.value}}}</p>
                      <p class="name">{{{_highlightResult.name.value}}}</p>
                      <div class="hit-description">{{{price}}}<b class="hit-currency">€</b></div>
                  </div>
              </div>
              <br>`,
      },
    }),
  ]),
  index({/* suggestions */}),
  federatedRefinement({/* categories */})
]);

Display and format query suggestions

For our query suggestions, we don’t want to use the default hits format. To override the default format, we have to create a custom render function, renderQSHits. To create a widget from this function, we use the connectHits function, and pass it our renderQSHits function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Customize UI of the Query Suggestion Hits
const renderQSHits = ({ widgetParams, hits }, isFirstRender) => {
 const container = document.querySelector(widgetParams.container);
 
 container.innerHTML = `<ul>
   ${hits
     .map(
       (item) => `
       <li>${instantsearch.highlight({ hit: item, attribute: 'query' })}</li>
     `
     )
     .join('')}
 </ul>`;
};
 
const QSHits = connectHits(renderQSHits);

Then, add and configure the query suggestions index widget, which targets the query_suggestions index containing your query suggestions. This widget uses our custom QSHits widget to display the hits.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
search.addWidgets([
  searchBox({ /* search box */ }),
  index({ /* products */ }),
  index({
    indexName: 'query_suggestions',
  }).addWidgets([
    configure({
      hitsPerPage: 16,
    }),
    QSHits({
      container: '#suggestions',
    }),
  ]),
  federatedRefinement({ /* categories */ }),
]);

Display and format the facets

We’ll also create a renderFederatedRefinement function to customize how we render our refinements. To create a refinement widget from this function, we pass it to the connectRefinementList function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Customize UI of the facet column
const renderFederatedRefinement = ({ widgetParams, items }, isFirstRender) => {
 const container = document.querySelector(widgetParams.container);
 
 container.innerHTML = `<ul>
   ${items
     .map(
       (item) => `
       <li>${item.label}</li>
     `
     )
     .join('')}
 </ul>`;
};
 
const federatedRefinement = connectRefinementList(renderFederatedRefinement);

Finally, we have to add and configure our custom federatedRefinement widget for the container with the categories ID to display our main index’s facets.

1
2
3
4
5
6
7
8
9
10
11
// Add the widgets
search.addWidgets([
  searchBox({ /* search box */ }),
  index({ /* products */ }),
  index({ /* suggestions */ }),
  federatedRefinement({
    attribute: 'categories',
    container: '#categories',
    limit: 15,
  }),
]);

Next steps

This solution provides the framework for any federated search. The next step is to take this solution and build a full search, browse, and discovery experience that fully fits your needs. There are many ways to do this, but it all comes down to combining different, but related, content on the same screen, and formatting each set of results in the way that highlights their relevance.

Ultimately, a full federated interface is a launching pad to discover the variety of your online catalog, and it should encourage your users to go further and deeper into your online platform.

Did you find this page helpful?