Icon indexing white

Search on top of your Firebase data with Algolia

Last updated 14 September 2017

Introduction

Algolia is a great option for adding search capabilities to your Firebase Realtime Database. Algolia gives you full-text search, typo tolerance and support for more advanced features like filtering and faceting right out of the box.

This tutorial will show you how to import data from Firebase to Algolia, index new data as it is added to Firebase, and remove indexed data when it is removed from Firebase.

In this example, we’ll use Algolia’s Node.js client as well as the firebase-admin JavaScript client library from Firebase.

Prerequisites

Familiarity with Firebase

This tutorial assumes you are familiar with Firebase, how it works, and how to build Firebase applications. If you would like to learn more before continuing with this tutorial, we suggest reading the following documentation and tutorials:

Create a Firebase Application

Create a new Realtime Database, or you can use one that already exists. We’ll be using the ref “contacts” for the whole example, so make sure there isn’t any data there already.

Create an Algolia Application

Create a new Algolia application, or use one that already exists. We’ll be using an index called “contacts”, so make sure that doesn’t already exist.

Create a Node.js Application

We’ll create a Node.js application that will listen to changes in Firebase and push them to Algolia. Once you’ve created it, it can then be run anywhere like Heroku, Nodejitsu, AWS, Azure, etc., to keep Algolia up to date from your production environment.

Create a new folder, change into it, and run npm init. This will take you through a wizard to create a package.json file. Accepting the defaults is ok.

Now let’s add the dependencies you will need from the command line.

npm install dotenv --save
npm install algoliasearch --save
npm install firebase-admin --save

Configure your environment

Create a file called .env. Substitute your values for the placeholders:

ALGOLIA_APP_ID=<algolia-app-id>
ALGOLIA_API_KEY=<algolia-api-key>
FIREBASE_DATABASE_URL=https://<my-firebase-database>.firebaseio.com

Make sure the Algolia API key you’ve chosen has write access. If in doubt, use your Admin API Key. Because we’re in a Node.js application and not the browser, the key will not be shared with any clients.

Download a service account JSON file from Firebase. This will be used to authenticate you as an admin so you can read and write data. From the Firebase console for your database, click the gear icon and choose “Project Settings”. Go to the “Service Accounts” tab. Click “Generate New Private Key”. Move the downloaded file into your directory and name it serviceAccountKey.json.

Create

Create a file called index.js inside of this directory. Add this code to that file:

var dotenv = require('dotenv');
var firebaseAdmin = require("firebase-admin");
var algoliasearch = require('algoliasearch');

// load values from the .env file in this directory into process.env
dotenv.load();

// configure firebase
var serviceAccount = require("./serviceAccountKey.json");
firebaseAdmin.initializeApp({
  credential: firebaseAdmin.credential.cert(serviceAccount),
  databaseURL: process.env.FIREBASE_DATABASE_URL
});
var database = firebaseAdmin.database();

// configure algolia
var algolia = algoliasearch(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_API_KEY);
var index = algolia.initIndex('contacts');

Load Contacts Into Firebase

Our example application will deal with simple contact records that contain only a name and a city.

Let’s add some contacts now so that we have something to sync in the next steps.

Paste this code underneath the code above:

// listening is all configured, let's add some contacts
Promise.all([
  database.ref('/contacts/josh').set({
    name: 'Josh',
    city: 'San Francisco'
  }),
  database.ref('/contacts/tim').set({
    name: 'Tim',
    city: 'Paris'
  })]).then(function() {
    console.log("Contacts loaded to firebase");
    process.exit(0);
  }).catch((function(error) {
    console.error("Error loading firebase", error);
    process.exit(-1);
  }));

Save the file and run:

node index.js

You should see “Contacts loaded to Firebase” printed in the console if everything was successful.

First-time import to Algolia

First, remove the code from the previous step from your index.js file. We won’t need it anymore, now that the contacts are in Firebase.

Add this code instead. This code iterates over each key in the contacts path of the Firebase database and adds each object to an array. That array is then sent to Algolia in one operation, which is more efficient than sending one record at a time.

var contactsRef = database.ref("/contacts");
contactsRef.once('value', initialImport);
function initialImport(dataSnapshot) {
  // Array of data to index
  var objectsToIndex = [];
  // Get all objects
  var values = dataSnapshot.val();
  // Process each child Firebase object
  dataSnapshot.forEach((function(childSnapshot) {
    // get the key and data from the snapshot
    var childKey = childSnapshot.key;
    var childData = childSnapshot.val();
    // Specify Algolia's objectID using the Firebase object key
    childData.objectID = childKey;
    // Add object for indexing
    objectsToIndex.push(childData);
  }))
  // Add or update new objects
  index.saveObjects(objectsToIndex, function(err, content) {
    if (err) {
      throw err;
    }
    console.log('Firebase -> Algolia import done');
    process.exit(0);
  });
}

Save the file and run as before:

node index.js

Once the program has completed successfully, you can verify in your Algolia dashboard that the contacts are there. You can index 1,000 or 10,000 objects at a time using this method, depending on the object size.

Ongoing live sync to Algolia

In a real application, you will want to listen for Firebase changes and index them as they come in.

Remove the initial import code from the previous section and replace it with this:

var contactsRef = database.ref("/contacts");

contactsRef.on('child_added', addOrUpdateIndexRecord);
contactsRef.on('child_changed', addOrUpdateIndexRecord);
contactsRef.on('child_removed', deleteIndexRecord);

function addOrUpdateIndexRecord(dataSnapshot) {
  // Get Firebase object
  var firebaseObject = dataSnapshot.val();
  // Specify Algolia's objectID using the Firebase object key
  firebaseObject.objectID = dataSnapshot.key;
  // Add or update object
  index.saveObject(firebaseObject, function(err, content) {
    if (err) {
      throw err;
    }
    console.log('Firebase object indexed in Algolia', firebaseObject.objectID);
  });
}

function deleteIndexRecord(dataSnapshot) {
  // Get Algolia's objectID from the Firebase object key
  var objectID = dataSnapshot.key;
  // Remove the object from Algolia
  index.deleteObject(objectID, function(err, content) {
    if (err) {
      throw err;
    }
    console.log('Firebase object deleted from Algolia', objectID);
  });
}

Save the file and run as before:

node index.js

This code uses the Firebase on method to listen for changes to children, and then triggers the appropriate method to sync the update to Algolia, whether that is an add, update or delete.

This is the configuration you would want to run in a background worker to keep your Algolia index up to date with your production Firebase data.

Caveats

The code in this example shows you how to sync one, 1-child deep node of your Firebase tree. It does not automatically index an entire nested Firebase database. However, all of the patterns that you need to do that are in here.

We recommend maintaining a separate set of scripts for indexing your Firebase data the first time or later down the road if you should need to reindex. These will probably look different than the scripts that you use to listen to and index changes regularly in production.

If you do decide that you need a full reindex, but don’t want to clear your production index while your application is live, we recommend creating a new index and indexing your Firebase there first. Then, use a moveIndex operation to rename the new index to the old one. This will avoid any production searches to an index in an incomplete state.

More resources

© Algolia - Privacy Policy