Suggestive Search w/ Firebase Cloud Functions, ElasticSearch + Angular

Firebase is an amazing service, but as you may already be aware there’s no out-of-the-box method for ‘fuzzy’ or partial-text search on documents (yet). Although, we have some options. Some seem a bit overkill - Going so far as to create arrays of strings to compare against all possible search queries.

Then we have the Firebase Docs suggested solution: Algolia, or more specifically, angular-instantsearch. If you’re looking for an Angular based solution then this is a solid option with a few caveats:

  • “Kitchen Sink” approach can be overwhelming, requiring features and dependencies you might not need (but still add to your size budget…)
  • Not 100% Polymorphic (yet) - Still has some issues w/ SSR
  • Can become costly depending on your data circumstances

So, as an alternative approach to using angular-instantsearch » Let’s build a simple Suggestive Search system using ElasticSearch on Google Cloud Platform w/ Firebase Cloud Functions, and then we’ll user Firestore and Angular to tie it all together. Ready? 🤩

Requirements

  • Google Cloud Account
  • Firebase Project (Blaze Plan)
  • Latest Angular (8, presently)

Deploy ElasticSearch

Launch ElasticSearch on Google Cloud

When that finishes up we’ll have a fresh set of credentials and an address:

ElasticSearch Server Credentials

Setup Cloud Functions

Add your ElasticSearch server credentials and address to Firebase:

> firebase functions:config:set elasticsearch.username="user" elasticsearch.password="PASSWORD" elasticsearch.url="http://IP_ADDRESS/elasticsearch"

Install Cloud Functions dependencies:

> npm install request request-promise --save

Now let’s use Cloud Functions to add event listeners to the contacts Firestore Collection. These listeners will then correspond each action over to our ElasticSearch server whenever a Document gets Created, Updated or Deleted.

/functions/src/index.ts

Search is also fairly straight forward, as has been working with ElasticSearch in general - We pass in our query and configure the search string and data fields, and then fire off the request. This is a really basic example, but you really have a ton of options available to you for doing search exactly how you want... The ElasticSearch documentation is well done, and a terrific place to learn more.

Let’s deploy … 🚀

> firebase deploy --only functions

Seed Data » FakerJS

Now we need some test data… FakerJS is perfect for that:

/functions/seed.ts

But, in order for us to use Firebase Admin Tools to seed our contact data we’ll need to generate… You guessed it. More credentials! 🎉

Follow this 2-Step Firebase guide to Initialize the SDK, and then save the file generated as: /tmp/serviceAccountKey.json

/tmp/serviceAccountKey.json

Please be careful with this info — Do Not Include In Your Repo!

By default, Angular CLI generated projects ignore the /tmp directory in .gitignore. So, if you’re using Angular CLI — Great. If not, or if you removed that item previously, then add /tmp to your .gitignore file.

Now let’s use Node to seed some contact data:

> node functions/seed.ts

Run that as many times as you want, it’ll add 5 random contacts.

Confirm + Preview » Dejavu Extension

Dejavu Chrome Extension

The first ElasticSearch Chrome Extension that I clicked on was Dejavu, and boy, am I sure happy I did… While it’s part of the Appbase.io product suite, Dejavu also functions quite well with other non-Appbase.io instances of ElasticSearch servers » Sweet… It has all the features we need to verify that everything is functioning properly — Even includes a built in Search Preview to help us verify the results of our Angular frontend.

Getting started is a breeze: Just add the server address with your credentials and specify the index name.

We can now see our contact data and run test queries. 👍

Angular Service + Component

Finally, we’ll add our Angular frontend to wrap things up.

The Search Service fetches data from our ElasticSearch server by calling the searchContacts Cloud Function. By default we get 10 response items, but for simplicity, we’ll shrink that down to 5 in the client.

/services/search.service.ts

The Component and Template accept and debounce user input, highlight results strings within response items and maintain visual state using the loading and no-results elements.

/components/search/search.component.ts
/components/search/search.component.html

Here’s the styles too, search.component.scss.

Voila!

Conclusion

Does it seem like a bit much to jump through all these hoops for fuzzy search? Sure, it would certainly be a very welcomed feature to the Firebase offering (and it would probably make quite a few people happy), but after putting the pieces together… You start to get an idea as to why it’s not included already.

Using a Cloud Function to perform search feels good — like it makes sense — it allows us to keep our credentials tucked away and secure, and avoids us having to mess around w/ CORS on our ElasticSearch server as well.

This will work in SSR environments w/ zero additional configuration, and really, that’s what makes it worth jumping through the hoops for me.

Liked the article? Questions? Ideas? Feel free to reach out!

Full-Stack JS developer » UI / UX connoisseur » Lover of Hockey and Bull Terriers

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store