TOCHA STARTED AS A PERSONAL PROJECT AND WAS VERY USEFUL TO BUILD PROTOTYPES AND SMALL APPS WHILE KEEPING FIREBASE PROJECTS ON THE SPARK PLAN.
STARTING AUGUST 17 2020, CLOUD FUNCTIONS FOR FIREBASE WILL DEPRECATE THEIR NODE 8 RUNTIME AND DEPLOYING NODE 10 FUNCTIONS WILL REQUIRE BILLING, WHICH MEANS CLOUD FUNCTIONS WILL NO LONGER BE AVAILABLE ON THE SPARK PLAN. BECAUSE TOCHA RELIES ON CLOUD FUNCTIONS, THIS MEANS THIS PROJECT WILL NO LONGER BE ABLE TO SERVE ITS PURPOSE.
I'VE DECIDED THAT THE BEST THING TO DO IS TO DEPRECATE THE PROJECT. CONSIDER ARRAY-CONTAINS
AS A FREE WORKAROUND TO IMPLEMENT TEXT SEARCH IN FIRESTORE (SEE THIS BLOG POST).
THANK YOU FOR EVERYONE WHO CONTRIBUTED, SUPPORTED OR USED 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.
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.
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.)
-
Create a new directory on your local machine for the project and navigate into it:
mkdir my_tocha_project cd my_tocha_project
-
If you haven't already, login to Firebase:
firebase login
This will launch a web page for you to authenticate your Firebase Account.
-
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
andindex.js
. -
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" } }
-
Install Tocha using:
npm install tocha
-
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 ...
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
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."
}
}
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
}
}
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:
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 query operator (can be<
,<=
,==
,>
,>=
,array-contains
,in
, orarray-contains-any
).val
orvalue
- the value to filter on.
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": "==",
"value": "randomUserUID"
}
// Optionally, you can add more filter maps here.
]
}
See a full list of valid filters and query limitations on the Firebase Documentation.
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 ordesc
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 get only the first n
documents retrieved, where n
is the positive number
you pass as value of the field.
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
}
The limitToLast
field allows you to get only the last n
documents retrieved, where n
is the positive number
you pass as value of the field.
Please note that you need at least one orderBy
field to use limitToLast
, otherwise it will return an exception.
If you need to sort/filter your data before performing a search, you can add these optional parameters to your query:
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.
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": "[email protected]"
}
Anyone and everyone is welcome to contribute. Please take a moment to review the Contributing Guidelines.
This project is licensed under the MIT LICENSE.
- This project makes use of the Lunr.js library by Oliver Nightingale.