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? 🤩
- Google Cloud Account
- Firebase Project (Blaze Plan)
- Latest Angular (8, presently)
- Open up your project on Google Cloud: console.cloud.google.com
- Find “Elasticsearch Certified by Bitnami” on the Martketplace and click Launch on Compute Engine.
- Select micro Machine type and then Deploy:
- Assign a static IP to the ElasticSearch VM by following these instructions, as ephemeral will change over time. (Optional)
When that finishes up we’ll have a fresh set of credentials and an address:
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.
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:
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:
Please be careful with this info — Do Not Include In Your Repo!
By default, Angular CLI generated projects ignore the
.gitignore. So, if you’re using Angular CLI — Great. If not, or if you removed that item previously, then add
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
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.
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.
Here’s the styles too, search.component.scss.
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!