How to Implement Firebase Full-Text Search

Full-text search is an essential feature for modern applications, enabling users to find relevant data quickly and efficiently. While Firebase Firestore doesn’t support full-text search, we can implement basic search functionality using Firestore queries.

The Search Functionality

In Vue project,

  • Create new folder name composables
  • In that folder create a new file getCollectionSearch.js and getCollection.js:

getCollectionSearch.js

import { projectFirestore } from "@/firebase/config";
import {
  collection,
  onSnapshot,
  query,
  orderBy,
  endAt,
  startAt,
  limit,
} from "firebase/firestore";
import { ref, watchEffect } from "vue";

const getCollectionSearch = (collectionName, selector, field) => {
  const documents = ref([]);
  const error = ref(null);
  const isPending = ref(false);
  let querySnapshot;
  const collectionRef = collection(projectFirestore, `${collectionName}`);

  if (selector) {
    querySnapshot = query(
      collectionRef,
      orderBy(field),
      startAt(selector),
      endAt(selector + "\uf8ff"),
      limit(100)
    );
  } else {
    querySnapshot = query(
      collectionRef,
      orderBy("createdAt", "desc"),
      limit(100)
    );
  }
  const unsubscribe = onSnapshot(
    querySnapshot,
    (snapshot) => {
      isPending.value = true;
      const results = [];
      snapshot.docs.forEach((doc) => {
        results.push({ id: doc.id, ...doc.data() });
      });

      documents.value = results;
      isPending.value = false;
      error.value = null;
    },

    (err) => {
      console.log("err", err);
      error.value = err.message;

      isPending.value = false;
      documents.value = null;
    }
  );

  watchEffect((onInvalidate) => {
    onInvalidate(() => unsubscribe());
  });

  return {
    documents,
    error,
    isPending,
  };
};

export default getCollectionSearch;

getCollection.js

import { collection, query, getDocs } from "firebase/firestore";
import { projectFirestore } from "@/firebase/config";
import { onMounted, ref, watchEffect } from "vue";

export const getCollectionQuery = (collectionName, whereDoc) => {
  const collectionRef = collection(projectFirestore, collectionName);
  const documents = ref([]);
  const isLoading = ref(true)

  // Create a query based on whereDoc (if provided)
  let queryRef = collectionRef;
  if (whereDoc) {
    queryRef = query(collectionRef, ...whereDoc);
  }

  const unsubscribe = async () => {
    try {
        const snapshot = await getDocs(queryRef);
        const data = [];
        snapshot.forEach((doc) => {
          data.push({ id: doc.id, ...doc.data() });
        });
        documents.value = data;
        console.log(documents.value)
      } catch (error) {
        console.error("Error in getCollectionQuery:", error);
    }finally{
        isLoading.value = false
    }
  }

  onMounted(() => {
    unsubscribe()
  })
  
  watchEffect((onInvalidate) => {
    onInvalidate(() => unsubscribe());
  });

  return {
    documents,
    isLoading
  };
};

Usage in a Vue Component

Here’s how you can integrate the getCollectionSearch and getCollection function into a Vue component:

<template>
  <div class="container">
    <h1>Full Text Search Using Firebase</h1>
    <input v-model="searchText" type="text" placeholder="Type here..." />

    <div v-if="!isLoading" class="list">
      <div v-for="item in categories" :key="item.id">{{ item.name }}</div>
    </div>

    <div v-if="isLoading" class="loading">
      <p>Loading....</p>
    </div>
  </div>
</template>

<script setup>
import { ref, watch } from "vue";
import { getCollectionQuery } from "./composables/getCollection";
import getCollectionSearch from "./composables/getCollectionSearch";
const categories = ref([]);
const searchText = ref("");

const { documents, isLoading } = getCollectionQuery("categories", []);

watch(documents, () => {
  if (documents.value.length > 0) {
    categories.value = documents.value;
  }
});

watch(searchText, () => {
  if (searchText.value) {
    const { documents: docs } = getCollectionSearch(
      `categories`,
      searchText.value.trim().toLocaleLowerCase(),
      "nameLowerCase"
    );

    watch(docs, () => {
      categories.value = docs.value;
    });
  } else {
    categories.value = documents.value;
  }
});
</script>

Optimizing Full-Text Search

For more advanced search capabilities, consider integrating Firebase with Algolia, a third-party search service offering:

  • Full-text search
  • Synonym matching
  • Ranking and faceting

Preview!

Source Code

https://github.com/NhaScript/firebase-full-text-search