Sync Firestore with Algolia in 2 minutes

Reddit
Linkedin

Yes, literally 2 minutes 😲

To think in Algolia and Firestore it's like to think in super couples, Batman and Robin, Messi and Iniesta, Kobe and Shaq, Rapinoe and Horan, you name it, it's just Firestore on steroids. Let's see some basics and how you can sync your firestore collection with algolia in less than a couple minutes (literally).

You can get the repo here.

Disclaimer: I'm not afiliated in any way with Algolia, I just think it is a good product and on this serverless era, it's a tool you definitively want to have under your belt.

Algolia 101

A few stuff you need to know, really quickly, as they defined:

Algolia is a hosted search engine capable of delivering real-time results from the first keystroke.
  • It's a data caching layer.
  • Provide all those features we lack on in firestore suit like: keystroke search, selective properties query, multi collection queries, etc (we are going to talk in detail later), ranking data.
  • Their product is based on a set of tool composed (but not limited) by a sdk and several ready to use (plug and play) UI components.
  • Detailed Analytics about user searchs / behavior.

Terminology

  • Collections are called Indices.
  • Build means to index data.
  • Facet just means grouping attributes (like categories or group).

The code

Ok enough, let's code. In order to sync a collection with an index we need to:

  • Create an Algolia Api Key.
  • Create a Cloud Function with some code.
  • Deploy.

Algolia Api Key.

After you create your account, you will show a dashboard like the following, you have a few default keys, which is recommended to keep secret, so we are going to create one specially for this purpose. We will end up with two important pieces of data, the App Key and the Secret Key.

algolia-dashboard

What is really cool about it is that you can set search and/or write permission to certain indexes while keeping other private, also you can set your key to work only from certain domain, which makes it secure to leave it to public on your front end.

algolia-keyset

Following the same pattern, you can create all the keys you need. For this example we are setting the addObject and deleteObject to our key

algolia-allkeys

That's all! notice you don't need to create any index in advanced and you would need specific permission to delete indexes deleteIndex, this add an extra layer of security.

Dependencies

Assuming you are going to sync one collection to one index, you just need to get when a doc is Created, Updated and Deleted... probably this already ring a bell, right? ;-) the onWrite trigger will be your best friend here.

We are going to sync a collection called adults with a index called the same. Note: Names don't need to match, in fact, you can set something like dev_adults and prod_adults, you get the idea.

On your firebase functions project folder.

npm install algoliasearch --save

If you are using typescript

npm install @types/algoliasearch --save-dev

The Function

And the snippet looks like this:

import * as functions from 'firebase-functions'
import * as algoliasearch from 'algoliasearch'

const env = functions.config()

const client = algoliasearch(env.algolia.appid, env.algolia.apikey)
const adultsIndex = client.initIndex(`adults`) // <-- Index name

  export const algoliaAdultsSync = functions
  .firestore.document(`adults/{doc}`).onWrite(async (change, _context) => {
    const oldData = change.before
    const newData = change.after
    const data = newData.data()
    const objectID = newData.id // <-- prop name is important

    if (!oldData.exists && newData.exists) {
        // creating
        return adultsIndex.addObject(Object.assign({}, {
          objectID
        }, data))
      } else if (!newData.exists && oldData.exists) {
        // deleting
        return adultsIndex.deleteObject(objectID)
      } else  {
        // updating
        return adultsIndex.saveObject(Object.assign({}, {
          objectID
        }, data))
    }
})

Important

The important part to understand here is the objectID, this will be used by algolia to keep track of the record, if you don't set it, they will assign one for you and it will be really hard for you to keep track of it, so we match it against the doc id and viola!

Also, the prop name is very important, it should be objectID not objectId (a lot of people make this mistake).

Setting the keys

This is the part where we use the keys we created on the last step, we would need the appKey and the secret key. On your console:

firebase functions:config:set algolia.appid="YOUR_APP_KEY" algolia.apikey="YOUR_SECRET_KEY"

If you cannot find them, I marked on the previous screenshot with red.

Deploy and Viola!

firebase deploy --only functions

Test

A gif is better than words:

algolia-allkeys

Leterally in less than 2 minutes 👻.

More

Ok, here is the thing, when people think about Algolia, for some reason, the feature that comes in mind if the keystroke search, that is really cool, but let me show you what you can do on the backend side:

Make sure you give search permission to your api key.

Selective properties queries

One of the biggest thing with firestore is the fact that you cannot make selective props query per document, it's all or nothing, but when using algolia we can do something like this:

import * as algoliasearch from 'algoliasearch'

const env = functions.config()

const client = algoliasearch(env.algolia.appid, env.algolia.apikey)
const adultsIndex = client.initIndex(`adults`)

const getByParams = async (attributesToRetrieve: string[], query: string, hitsPerPage: number = 10) => {
  return new Promise(async (resolve, reject) => {
    return adultsIndex.search({ query, attributesToRetrieve, hitsPerPage }, (e, { hits } = {}) => {
      if (e) return reject({ error: e })
      return resolve({ data: hits })
    })
  })
}

// now you can do something like:
(async () => {
  const { data } = getByParams(['name', 'email'], 'an', 5)
  // based on the test gif example
  // [{ name: 'andy', email: 'myemail@email.com', objectID: '22DUxxx' _highlightResult: { ... }]
})()

Multiple collections (indexes) queries.

Let's assume you have a two collections adults and children fully sync with 2 algolia indexes and you want you make a query based on both (or more) with the same params. You can do something like this:

import * as algoliasearch from 'algoliasearch'
import { flatMap } from 'lodash'

const env = functions.config()

const client = algoliasearch(env.algolia.appid, env.algolia.apikey)

const getByTwoCollections = async (indexesParams: []) => {
  return new Promise(async (resolve, reject) => {
    return client.search(indexesParams, (e, { results } = {}) => {
      if (e) return reject({ error: e })
      const data = flatMap(results.map(({ hits }) => hits))
      return resolve({ data })
    })
  })
}

// now you can do something like:
(async () => {
  const query = [{
    indexName: 'adults',
    query: 'an', // <--- this is param for this index
    params: {
      attributesToRetrieve: ['name'],
      hitsPerPage: 3,
    }
  },{
    indexName: 'children',
    query: 'an', // <-- it does not need to match the other index :O
    params: {
      attributesToRetrieve: ['name'],
      hitsPerPage: 3,
      filters: 'age <= 5' // <-- you can even filter by props
    }
  }]
  const { data } = getByTwoCollections(query)
  // [
  //  { name: 'andy', type: 'adults', objectID: '22DUxxx' _highlightResult: { ... }
  //  { name: 'andy child', age: 2, type: 'children', objectID: '22EFxxx' _highlightResult: { ... }
  //]
})()

Pretty cool eh?

Ready to use components

I won't extend too much on this, Algolia provides a set of ready to use components that makes easier to build complex search experience really easy, they also support popular frameworks like React, Angular, Vue, etc. Take a look at this and this.

Last

I'm trying to record a couple of videos series of tutorials, if you want a version of this post on video and make fun of my english, take a look and help me to get 100 subscribers so I can put a name on the channel and make my mom proud.

Enjoyed this post? Receive the next one in your inbox!

I hand pick all the best resources about Firebase and GCP around the web.


Not bullshit, not spam, just good content, promised 😘.


Reddit
Linkedin