Most apps allow users to search app content. For example, you may want to search for posts containing a certain word or notes you've written about a specific topic.
Cloud Firestore doesn't support native indexing or search for text fields in documents. Additionally, downloading an entire collection to search for fields client-side isn't practical.
To enable full text search of your Cloud Firestore data, use a dedicated third-party search service. These services provide advanced indexing and search capabilities far beyond what any simple database query can offer.
Before continuing, research then choose one of the search providers below:
Solution: External search service
Consider a note-taking app where each note is a document:
// /notes/${ID}
{
owner: "{UID}", // Firebase Authentication's User ID of note owner
text: "This is my first note!"
}
You can use Elastic with Cloud Functions to populate an index with the contents of each note and enable search. First, configure an Elastic client using your Elastic instance ID, username, and password:
Node.js
const { Client } = require("@elastic/elasticsearch"); // Initialize Elastic, requires installing Elastic dependencies: // https://github.com/elastic/elasticsearch-js // // ID, username, and password are stored in functions config variables const ELASTIC_ID = functions.config().elastic.id; const ELASTIC_USERNAME = functions.config().elastic.username; const ELASTIC_PASSWORD = functions.config().elastic.password; const client = new Client({ cloud: { id: ELASTIC_ID, username: ELASTIC_USERNAME, password: ELASTIC_PASSWORD, } });
Next, add a function that updates the index each time a note is written:
Node.js
// Update the search index every time a blog post is written. exports.onNoteCreated = functions.firestore.document('notes/{noteId}').onCreate(async (snap, context) => { // Get the note document const note = snap.data(); // Use the 'nodeId' path segment as the identifier for Elastic const id = context.params.noteId; // Write to the Elastic index client.index({ index: "notes", id, body: note, }); });
Once your data is indexed, you use a callable Cloud Function to search the index:
Node.js
exports.searchNotes = functions.https.onCall(async (data, context) => { const query = data.query; // Search for any notes where the text field contains the query text. // For more search examples see: // https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/search_examples.html const searchRes = await client.search({ index: "notes", body: { query: { query_string: { query: `*${query}*`, fields: [ "text" ] } } } }); // Each entry will have the following properties: // _score: a score for how well the item matches the search // _source: the original item data const hits = searchRes.body.hits.hits; const notes = hits.map(h => h["_source"]); return { notes: notes }; });
Then you can call the search function using the Firebase SDK:
Web
const searchNotes = firebase.functions().httpsCallable('searchNotes'); searchNotes({ query: query }) .then((result) => { const notes = result.data.notes; // ... });
Adding Security
The original solution works well if all notes are searchable by all users. However, many use cases require securely limiting results.
In this case the simplest security measure would be to check the uid
of the
user that calls the Cloud Function and then limit the search to notes where
the owner
field matches the user's ID.
Elastic also offers many authenticationand authorization strategies that you can use to provide role-based access to your search index. For more information see Demystifying authentication and authorization in Elasticsearch
Limitations
- Pricing: Elastic Cloud offers many service tiers, make sure to visit the pricing page and choose the option that best fits your needs.