Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Algolia Firestore QuickStart With Firebase Cloud Functions

Episode 109 written by Jeff Delaney
full courses for pro members

Health Check: This lesson was last reviewed on and tested with these packages:

  • Angular v6
  • RxJS v6.2
  • firebase-functions v1
  • algoliasearch v3

Find an issue? Let's fix it

Source code for Algolia Firestore QuickStart With Firebase Cloud Functions on Github

Almost a year ago, I created a set of lessons covering an Algolia + Angular + Firebase integration, but a lot has changed since then…

  • Firestore was announced.
  • Cloud Functions v1.0 was released.
  • Algolia released a full component library for Angular.
  • Angular 6 was released.


Live Demo

That means it’s time for a brand new lesson in full-text search with Algolia. Much like Firebase, I am a big fan of this platform because the developer experience is amazing. Algolia is used on this site… click the search icon in the top menu and give it a whirl. You might be surprised just how easy it is to build a sophisticated autocomplete full-text search feature into your existing app.

Demo of Algolia instantsearch with Firebase Cloud Firestore

Initial Setup

I will be using an Angular v6 project as the frontend for this lesson, but the backend code is compatible with any framework - React, Vue, Android, iOS, etc. The data indexing is handled by a NodeJS Cloud Function, which is completely isolated from the frontend. The prerequisites for this lesson include:

  • Firebase project with billing enabled (Blaze plan).
  • Firebase CLI tools installed.
  • Algolia account (free tier).

Initialize Cloud Functions

Initialize Firebase Cloud Functions and cd into the project:

firebase init functions
cd functions

Make sure to select TypeScript when prompted.

Create an Index and Set your API Key

Create an index on the Algolia dashboard - I’m calling mine zoo_search

Create an index on the Algolia dashboard

Next, go to the API key tab and grab your AppID and Admin API Key

Never use the Admin API Key in your frontend code (ie Angular). If somebody finds it, they will be able to wipe out your entire index in no time. It is only safe as a Cloud Function environment variable.

Grab the API keys for your project

Back in the Firebase Functions project, we need to set the Algolia credentials as environment variables.

firebase functions:config:set algolia.appid="your-appId" algolia.apikey="your-admin-api-key"

Backend: Indexing Data in Algolia with Firebase Cloud Functions

At this point, our index is empty. We need to add some data to it so we have something to search for in the frontend. Our database is a collection of zoo animals that looks like this.

Firestore data to be indexed in Algolia

Our goal is to mirror this data structure in Algolia. You only need to index properties that you need to be searchable by end users, but in this lesson we will index the entire document. Here’s what it looks like after the indexing is complete.

Firestore data after being indexed in Algolia

Algolia Firestore Cloud Function

First, we need to install Algolia’s Node SDK in the Cloud Function environment.

npm i --save algoliasearch

Then we can initialize it with the credentials we added to the environment.

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
const env = functions.config();

import * as algoliasearch from 'algoliasearch';

// Initialize the Algolia Client
const client = algoliasearch(env.algolia.appid, env.algolia.apikey);
const index = client.initIndex('zoo_search');

Index New Data on Create

This is about as easy as a Firebase Cloud Function gets - we access the data from a newly created document, then push it Algolia by returning the addObject Promise.

exports.indexAnimal = functions.firestore
.document('zoo/{animalId}')
.onCreate((snap, context) => {
const data = snap.data();
const objectID = snap.id;

// Add the data to the algolia index
return index.addObject({
objectID,
...data
});
});

Remove Old Data on Delete

Removing data from the index is just as easy. We are saving Algolia objects with an ID that matches their Firestore document ID, which allows us to reference them during updates are removals.

exports.unindexAnimal = functions.firestore
.document('zoo/{animalId}')
.onDelete((snap, context) => {
const objectId = snap.id;

// Delete an ID from the index
return index.deleteObject(objectId);
});

Deploy

Our functions are ready to deploy:

firebase deploy --only functions

The backend is complete! If you create a new document in Firestore under the zoo collection, it should automatically invoke the function and make it available in Algolia.

Frontend: Integration with Angular InstantSearch

Now it’s time to add some search widgets to the frontend app using Angular Instantsearch. This package provides a handful of pre-built components that you can drop into your app to handle full-text search, typeahead, autocomplete, filtering, pagination, etc. If you want your search feature built quickly, this is the package for you.

Start by installing the package:

npm i --save angular-instantsearch

And import it in your app.module.ts:

import { NgAisModule } from 'angular-instantsearch';

@NgModule({
imports: [
NgAisModule
],
})

Lastly, add your Algolia credentials to the environment.ts - make sure to use the Search Only API Key (not Admin).

export const environment = {
algolia: {
appId: 'app-id',
apiKey: 'search-only-key'
}
}

Required Polyfill

At the time of this lesson there is a small bug that causes Algolia to throw an error on initialization in Angular 6. The current workaround is to add the following code to polyfills.ts:

(window as any).process = {
env: { DEBUG: undefined },
};

search.component.ts

By default, Algolia will show the entire list of search hits before the use enters a query. We can hide the results by listening to the change event on the actual search form input. If the user’s input is blank, we will hide the results from the view.

import { Component } from '@angular/core';
import { environment } from '../environments/environment';

@Component({ ... })
export class AppComponent {

searchConfig = {
...environment.algolia,
indexName: 'zoo_search'
}

showResults = false;

constructor() { }

searchChanged(query) {
if (query.length) {
this.showResults = true;
} else {
this.showResults = false;
}
}

}

search.component.html

Algolia Instantsearch provides a variety of components that we can declare directly in the HTML. It is important to wrap all Algolia components inside ng-ais-instantsearch so your search feature has access to the index and credentials. When the user types into the form, Algolia will automatically filter the results and return an array of hits or results that contain data from the index. We can simply loop over these results and display our own custom template in the UI.

<ng-ais-instantsearch [config]="searchConfig">

<ng-ais-search-box (change)="searchChanged($event)"></ng-ais-search-box>

<ng-ais-hits *ngIf="showResults">
<ng-template let-hits="hits">
<div *ngFor="let hit of hits">

<img [src]="hit.avatar" width="30px">

<ng-ais-highlight attribute="name" [hit]="hit"></ng-ais-highlight>

<div class="bio">
{{ hit.bio | slice:0:30 }}...
</div>

</div>
</ng-template>
</ng-ais-hits>
</ng-ais-instantsearch>

Demo of Algolia instantsearch with Firebase Cloud Firestore

The End

Algolia is used for the search right here on AngularFirebase.com. It’s fast, easy, and reliable - and they don’t pay me to say that. When used with Firestore, it can help overcome many of the inherent limitations of multi-property filtering in a NoSQL database.