How to add fast realtime search to your Firebase app with Algolia

Ben Cochrane
ITNEXT
Published in
7 min readOct 1, 2018

--

One of the nicest ways to improve a users experience within your app is to provide full text search across a variety of lists or collections.

Algolia realtime search in action in an equipment management app using Firebase and Firestore

In this tutorial, I will run through how to implement realtime full-text search with Algolia, Firebase functions and Firestore (Firebase’s new realtime database).

You might be thinking, hang on — you just said Firestore is a realtime database — why would I need another realtime database to handle full-text search. Good question! The answer is Firestore is great at updating changes to a collection as they happen as it sets up listeners on whatever collection or single object you like. Firestore however isn’t designed to provide super-fast pre-indexed search results using free-text input. That’s where Algolia shines though.

Algolia is search-as-a-service. In short — it gives your app Google-quick full text search results. Anything you add to Algolia can be searched (even with spelling mistakes like I showed in the video above).

Overall, the steps to implement Algolia search are:

  1. Create an Algolia account algolia.com
  2. Create an index in Algolia (this just means what is the name of the database you would like to search that already exists in your app — in my Firestore database I had an “equipment” collection so I created an “equipment” index in Algolia.
  3. Create a firebase function that listens for creates and updates in your collection and copy them to Algolia
  4. Add some items to a Firestore collection to test it out
  5. Call Algolia on the front end of your app to display the search results

Let’s go through each of these steps shall we?

Step 1: Create an Algolia account

Not much else to say here just go to algolia.com and create a free account

Step 2: Create an index in Algolia

Once you’ve created your account, click Indices on the left hand panel then click “New Index”

Create a new index from the Indices page in Algolia

Name this index whatever you like, but I keep the index name the same as my Firestore collection name for simplicity.

Step 3: Create Firebase functions that listen for create and edits to the collection you want to search on

In my case, I want to know when a new piece of equipment gets added or edited. To do this these are the firebase functions I wrote.

Listen to CREATE and EDIT actions on the “equipment” collection in Firestore:

// this the firebase functions setup codeconst functions = require('firebase-functions');
const admin = require('firebase-admin');
let Promise = require('promise');
const cors = require('cors')({ origin: true });
const auth = require('basic-auth');
const request = require('request');
const algoliasearch = require('algoliasearch');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
// listen for creating a piece of equipment in Firestoreexports.addEquipmentToAlgolia = functions.firestore.document('equipment/{document}')
.onCreate(event => {
console.log('ADD EQUIP EVENT IS', event);const active = event.data.data().active === true ? "true" : "false"const data = {
objectID: event.params.document,
description: event.data.data().description,
category: event.data.data().category,
category_id: event.data.data().category_id,
flat_fee: event.data.data().flat_fee,
group: event.data.data().group,
hourly: event.data.data().hourly,
active: active,
daily: event.data.data().daily,
weekly: event.data.data().weekly,
monthly: event.data.data().monthly,
bulkItem: event.data.data().bulkItem
};
return addToAlgolia(data, 'equipment')
.then(res => console.log('SUCCESS ALGOLIA equipment ADD', res))
.catch(err => console.log('ERROR ALGOLIA equipment ADD', err));
});
// listen for editing a piece of equipment in Firestoreexports.editEquipmentToAlgolia = functions.firestore.document('equipment/{document}')
.onUpdate(event => {
console.log('edit event', event.data.data())const active = event.data.data().active === true ? "true" : "false"const data = {
objectID: event.params.document,
description: event.data.data().description,
category: event.data.data().category,
category_id: event.data.data().category_id,
flat_fee: event.data.data().flat_fee,
group: event.data.data().group,
hourly: event.data.data().hourly,
active: active,
bundleItem: event.data.data().bundleItem,
daily: event.data.data().daily,
weekly: event.data.data().weekly,
monthly: event.data.data().monthly,
bulkItem: event.data.data().bulkItem
};
console.log('DATA in is', data)return editToAlgolia(data, 'equipment')
.then(res => console.log('SUCCESS ALGOLIA EQUIPMENT EDIT', res))
.catch(err => console.log('ERROR ALGOLIA EQUIPMENT EDIT', err));
});
// listen for a delete of a piece of equipment in Firestoreexports.removeEquipmentFromAlgolia = functions.firestore.document('equipment/{document}')
.onDelete(event => {
const objectID = event.params.document;
return removeFromAlgolia(objectID, 'equipment')
.then(res => console.log('SUCCESS ALGOLIA equipment ADD', res))
.catch(err => console.log('ERROR ALGOLIA equipment ADD', err));
})
// helper functions for create, edit and delete in Firestore to replicate this in Algoliafunction addToAlgolia(object, indexName) {
console.log('GETS IN addToAlgolia')
console.log('object', object)
console.log('indexName', indexName)
const ALGOLIA_ID = functions.config().algolia.app_id;
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
const index = client.initIndex(indexName);
return new Promise((resolve, reject) => {
index.addObject(object)
.then(res => { console.log('res GOOD', res); resolve(res) })
.catch(err => { console.log('err BAD', err); reject(err) });
});
}
function editToAlgolia(object, indexName) {
const ALGOLIA_ID = functions.config().algolia.app_id;
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
const index = client.initIndex(indexName);
return new Promise((resolve, reject) => {
index.saveObject(object)
.then(res => { console.log('res GOOD', res); resolve(res) })
.catch(err => { console.log('err BAD', err); reject(err) });
});
}
function removeFromAlgolia(objectID, indexName) {
const ALGOLIA_ID = functions.config().algolia.app_id;
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
const index = client.initIndex(indexName);
return new Promise((resolve, reject) => {
index.deleteObject(objectID)
.then(res => { console.log('res GOOD', res); resolve(res) })
.catch(err => { console.log('err BAD', err); reject(err) });
});
}

The above code is located in your functions/index.js file in your firebase app.

Important: You will notice that there are references to functions.config().algolia.app_id — we configure this in the command line like this:

Inside of your app in the terminal, do the following commands (replacing YOUR_ALGOLIA_APP_ID with the Application ID in Algolia and YOUR_ADMIN_API_KEY with the Admin API Key in Algolia.

$ firebase functions:config:set algolia.app_id="YOUR_ALGOLIA_APP_ID" algolia.api_key="YOUR_ADMIN_API_KEY"

To find your keys, go to API Keys on the left hand panel in Algolia, and copy them.

Once you’ve added your keys to Firebase, you will now be able to access the keys to create, update and delete index objects in Algolia.

Important also: You will see in the top of the code snippet above, we are using the algoliasearch npm module. We will need to install this locally for our functions to deploy so let’s do that now. From the root of your app in the terminal:

$ cd functions$ yarn add algoliasearch

(To have the code working as shown above make sure you install the other packages shown too)

Step 4: Add some items to a Firestore collection to test it out

Let’s test make sure our function is now working. We first need to deploy our function:

$ firebase deploy --only functions

Next, go to the Firestore section of firebase and manually add an item into your collection, matching the payload that it expects in the function you just wrote from above.

Once you’ve added this new item in your Firestore collection, go to the Functions section of Firebase to view the logs to ensure a successful execution on the Firebase side (then we’ll go take a look in Algolia)

Successful function execution to copy a new Firestore item into Algolia

You might see something like above, showing Success in the console.log’s we added. I’ve opened up the DATA object to inspect the collection item just added.

Now let’s check Algolia to make sure that item is also there.

With some luck, our equipment item is also now copied into Algolia:

Our Firebase function has copied a firestore object into Algolia successfully

Now we have this working (and assuming our update and delete functions are also working, we are ready to search on the front end — yew!

Step 5: Call Algolia on the front end of your app to display the search results

First, let’s pull in the script tag giving us access to the client-side algolia methods. In index.html add:

<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>

The front-end of this particular app was written in Angular 1, so please ignore the “vm.” and “$scope” syntax for now as the function is largely identical for React etc.

// search algolia for equipment name
function doSearch() {
$scope.results = [];
const client = algoliasearch('Application ID', 'Search-Only API Key');
index = client.initIndex('equipment');
vm.newQuery = { query: vm.querySearch };index.search(vm.newQuery)
.then(res => {
if (vm.querySearch.length === 0) {
$scope.results = []
vm.currentFilter.description = '';
vm.currentFilter.category = '';
vm.showDropdown = false;
} else {
lodash.map(res.hits, item => item.name = item.description);
$scope.results = res.hits;
vm.currentFilter.category = '';
vm.showDropdown = true;
}
$scope.$apply();
})
.catch(err => console.error(err));
}

Above, we call the algoliasearch method (accessible from the script tag added above this snippet) which takes in our Application ID used earlier. The Search-Only API Key can be found in the API Keys section in Algolia above the Admin API Key — it’s important to use this key and to not expose your Admin API Key here.

Algolia is a super powerful tool. We haven’t even scratched the surface on how we would improve the ranking of results depending on your industry, customer requirements or any other factors we might like to add — maybe for a future tutorial.

Great work! We are all set up. You should now be able to call the Algolia database which is a live, and self-updating replica of your Firestore collection. And your users now have Google-fast search in your app!

If you have any questions on Algolia, Firebase Functions or Firestore I’m happy to help.

Ben :)

--

--