Full Text Search with Cloud Firestore and Algolia
At the end of January, Firebase announced that Cloud Firestore is moving out of beta and into general availability. Firestore is Firebase’s latest database solution, a NoSQL cloud database that’s designed to scale. Just like the Realtime Database before it, Firestore offers real time updates as well as offline support. Unlike Realtime Database, data is in Firestore is stored in documents and collections. (Find out which database is right for your project here)
Firestore doesn’t natively support full text search, but third party tools like Algolia are quite simple to set up. Algolia is a hosted search engine that enables you to build real time search interfaces with ease.
In order for Algolia to be able to search our Firestore data we must provide it to Algolia. Using Cloud Functions we can listen for changes to our database and then write them to our Algolia indices. Here’s the instructions to get started with Cloud Functions.
Image you have an app of apartment listings that we want to search. Our apartment documents in Firestore look like this:
// /apartments/${apartment_id}
{
address: "101 1st Street"
rent: 1100.0
sqft: 600
numBedrooms: 2
numBathrooms: 1
}
Using Firebase Cloud Functions we can listen to changes on our Firestore instance.
Our function will need access to our Algolia index. To do so we’ll take our Algolia credentials and add them to the Cloud Functions console. You’ll need the Application ID and the Admin API Key. You can add them using the following command:
$ firebase functions:config:set algolia.app_id="<YOUR_APP_ID>" algolia.api_key="<YOUR_API_KEY>"
Now we can access them in like so:
const ALGOLIA_ID = functions.config().algolia.app_id;
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
And we’ll need the following lines to bring in Algolia and Firestore:
var algoliasearch = require('algoliasearch');
const functions = require('firebase-functions');
We want to update our Algolia data whenever we add a new apartment, remove an apartment, or make a change to an apartment. We can do this by using Firestore Triggers.
First we’ll set up Algolia:
const ALGOLIA_INDEX_NAME = 'apartments';
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
Then we can start listening for changes.
Adding a new apartment document to Algolia:
exports.onApartmentCreated = functions.firestore.document('apartments/{apartmentId}').onCreate((snap, context) => {
// Get the apartment's document
const note = snap.data();
// Add an 'objectID' for Algolia
note.objectID = context.params.apartmentId;
// Write to the algolia index
const index = client.initIndex(ALGOLIA_INDEX_NAME);
return index.saveObject(note);
});
Deleting an apartment in Algolia:
exports.onApartmentDeleted = functions.firestore.document('apartments/{apartmentId}').onDelete((snap, context) => {
// Delete an apartment from the algolia index
const index = client.initIndex(ALGOLIA_INDEX_NAME);
return index.deleteObject(context.params.apartmentId);
});
Updating an apartment in Algolia:
exports.onApartmentEdited = functions.firestore.document('apartments/{apartmentId}').onUpdate((change, context) => {
const newValue = change.after.data();
const previousValue = change.before.data();
newValue.objectID = context.params.apartmentId;
// Write the update to the algolia index
const index = client.initIndex(ALGOLIA_INDEX_NAME);
return index.saveObject(newValue);
});
Now you’re all set! You can deploy your functions with this command:
$ firebase deploy --only functions
You should be able to see your functions in the Cloud Functions console. Now when you make a change to your Firestore the changes should be sent to Algolia! Algolia has several integrations to make building your search interface. Now all you have to do is integrate a search field to your project. Algolia has tools to build search UIs for Android, iOS, React, Vue, and VanillaJS. Find out more here: https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/android/
Here’s our full file:
var algoliasearch = require('algoliasearch');
const functions = require('firebase-functions');
const ALGOLIA_ID = functions.config().algolia.app_id;const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
const ALGOLIA_INDEX_NAME = 'apartments';
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
//ADD APARTMENT
exports.onApartmentCreated = functions.firestore.document('apartments/{apartmentId}').onCreate((snap, context) => {
// Get the apartment's document
const note = snap.data();
// Add an 'objectID' for Algolia
note.objectID = context.params.apartmentId;
// Write to the algolia index
const index = client.initIndex(ALGOLIA_INDEX_NAME);
return index.saveObject(note);
});
//REMOVE APARTMENT
exports.onApartmentDeleted = functions.firestore.document('apartments/{apartmentId}').onDelete((snap, context) => {
// Delete an apartment from the algolia index
const index = client.initIndex(ALGOLIA_INDEX_NAME);
return index.deleteObject(context.params.apartmentId);
});
//EDIT APARTMENT
exports.onApartmentEdited = functions.firestore.document('apartments/{apartmentId}').onUpdate((change, context) => {
const newValue = change.after.data();
const previousValue = change.before.data();
newValue.objectID = context.params.apartmentId;
// Write the update to the algolia index
const index = client.initIndex(ALGOLIA_INDEX_NAME);
return index.saveObject(newValue);
});