Skip to content
🔥 Full-Text Search for Firebase Projects that use the Spark (Free) plan.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore
.travis.yml
CONTRIBUTING.md
LICENSE
README.md
index.js
package.json

README.md

Build Status

Tocha

Full-Text Search for Firebase Projects using the Spark (Free) plan (no billing enabled).

Use Tocha if you want to implement full-text search on:

  • Android/iOS App Prototypes;
  • Small Android/iOS Apps using the Firebase Spark Plan.

If your app has scaled and you're using a plan with billing enabled (Flame or Blaze), then this library is not for you. Instead, prefer using the solution recommended on the Firebase Documentation.

Using Tocha on web (JavaScript) applications is also not recommended, as it might be redundant. If you want to implement full-text search on your js app, consider using Lunr.js.

Getting Started

Prerequisites

To install and deploy Tocha, you'll need:

  • Node.js, which comes with the Node Package Manager (npm);
  • The Firebase CLI which can be installed using npm install -g firebase-tools. See full installation details on the Documentation.

Installing

Tocha runs on Cloud Functions, so you'll need to install the Firebase CLI (as instructed on the Prerequisites) in order to setup Cloud Functions for your Firebase Project.

(If you know how to deploy Cloud Functions to Firebase and have already done so, you may skip to step 4.)

  1. Create a new directory on your local machine for the project and navigate into it:

    mkdir my_tocha_project
    cd my_tocha_project
  2. If you haven't already, login to Firebase:

    firebase login

    This will launch a web page for you to authenticate your Firebase Account.

  3. Initialize and configure your Firebase Project. In this step, you'll be asked what project you'll be using and what features you would like to setup (be sure to select functions at least).

    firebase init

    If successful, this should create 2 files under your project directory: package.json and index.js.

  4. Open the package.json file on your favorite text editor and make sure you have set node version to 8:

    {
      // ... name, description, dependencies, etc
      "engines": {
        "node": "8"
      }
    }
  5. Install Tocha using:

    npm install tocha
  6. Open the index.js file on the text editor, import Tocha and create the functions you need:

    const functions = require('firebase-functions');
    // ... you may have more imports here ...
    const tocha = require('tocha');
    
    // Add this line to enable Full-Text Search for Cloud Firestore
    exports.searchFirestore = tocha.searchFirestore;
    
    // Add this line to enable Full-Text Search for the Realtime Database
    exports.searchRTDB = tocha.searchRTDB;
    
    // ... you may have more cloud functions here ...

Deployment

To deploy your functions to Firebase, you can either:

  • deploy the cloud functions and all the other tools you have enabled for that project:
firebase deploy
  • or deploy the cloud functions only
firebase deploy --only functions

Example

Let's say we have a Firestore Collection named "notes" with the following Documents:

{
  "note1": {
    "title": "Remember to buy butter",
    "description": "Valeria asked me to get some butter at the supermarket on my way home."
  },
  "note2": {
    "title": "Eta's birthday coming up",
    "description": "Eta is turning 28 this Friday. Don't forget to call her wishing HBD."
  }
}

Performing a Simple Search

In order to search the collection, you'll need to create a new collection named "tocha_searches" and add a new document to it. This document should contain the following fields:

  • collectionName - the name of the collection to be searched;
  • fields - array of fields to search on.
  • query - the word/expression you're looking for.

Example 1: Let's run an exact search for notes with the word "butter". Our document would look like this:

{
  "collectionName": "notes",
  "fields": ["title"],
  "query": "butter"
}

Example 2: Sometimes you may need an inexact search. Let's look for the note about Eta's birthday:

{
  "collectionName": "notes",
  "fields": ["title", "description"],
  "query": "Eta*"
}

Notice that on the last example we've used the wildcard *. You can find the list of all possible wildcards, boosts and fuzzy matchings here.


Adding that document to the collection should trigger our Cloud Function which adds a response field to it. This field is an array of matches. Each match contains the following fields:

  • id - the id of the document that matches our query;
  • score - the relevance of the document, calculated using the BM25 algorithm. Find out more here.
  • data - the actual document returned by our query.

So our document from Example 1 would become:

{
  "collectionName": "notes",
  "fields": ["title"],
  "query": "butter",
  "response": {
    "result": [
      {
        "id": "note1",
        "score": 0.856,
        "data": {
          "title": "Remember to buy butter",
          "description": "Valeria asked me to get some butter at the supermarket on my way home."
        }
      }
    ],
    "isSuccessful": true
  }
}

Advanced Search on Cloud Firestore

Although running a text-search in the whole collection is great, sometimes you may need to filter this collection before running the search. And to do that, you can use these optional parameters:

where

An array of map values where you can perform simple or compound queries for firestore. Each map in this array must contain the following fields:

  • field - the field to filter on.
  • operator - a comparison operator. This can be <, <=, ==, >, >=, or array_contains.
  • val - the value.

Example: Suppose our notes had one more field named ownerUID, which tells us which user created the note.

We might want to query only on the notes created by a specific user (uid: randomUserUID). To do that, we can use:

{
  // ... Other Fields (collectionName, fields, query, etc)
  "where": [
    {
      "field": "ownerUID",
      "operator": "==",
      "val": "randomUserUID"
    }
    // Optionally, you can add more filter maps here.
  ]
}

See a full list of valid filters and query limitations on the Firebase Documentation.

orderBy and limit

The orderBy field allows you to order the result of your query. This field is an array of map objects with 2 fields:

  • field - the field to sort on.
  • direction (optional) - asc for ascending order or desc for descending. If you omit this field, it will use ascending order.

Note: If you want to order by multiple fields you might need to Create an Index on Firestore.

The limit field allows you to limit the number of documents retrieved. This value of this field must be a number.

Example: Let's order our notes by title and get the first 5:

{
  // ... Other Fields (collectionName, fields, query, etc)
  "orderBy": [
    {
      "field": "title",
      "direction": "desc"
    }
    // Optionally, you can add more orderBy maps here.
  ],
  "limit": 5
}

Advanced Search on The Realtime Database

If you need to sort/filter your data before performing a search, you can add these optional parameters to your query:

orderChild, orderValue, orderKey

Those are equivalent to orderByChild(), orderByValue() and orderByKey(). You can find more about it on the documentation.

Example usage:

{
  // ... Other Fields (collectionName, fields, query, etc)
  "orderValue": true,
  "orderKey": true,
  "orderChild": "birthday" // ordering by the birthday child
}

Please note that you can only use one order-by method at a time. Calling an order-by method multiple times in the same query throws an error.

limitFirst, limitLast, startAtBound, endAtBound, equalToBound

Equivalent to limitToFirst(), limitToLast(), startAt(), endAt() and equalTo(). See the documentation for more details.

Example usage:

{
  // ... Other Fields (collectionName, fields, query, etc)
  "limitFirst": 10,
  "startAtBound": 5,
  "equalToBound": "john.doe@tocha.com"
}

Contributing

Anyone and everyone is welcome to contribute. Please take a moment to review the Contributing Guidelines.

License

This project is licensed under the MIT LICENSE.

Acknowledgments

You can’t perform that action at this time.