DEV Community

Cover image for Full text search with Firestore & Melli search - Implementing Full text search with MeiliSearch in React/Next JS
Gautham Vijayan
Gautham Vijayan

Posted on

Full text search with Firestore & Melli search - Implementing Full text search with MeiliSearch in React/Next JS

In this post, we will be looking into how you can create a search bar interface in your React/Next JS application. We will be creating a Next JS project (as recommended by the React team) and connect our melli search instance with our frontend and create a nice search interface. We will be using tailwind to style the project.(You can use any CSS framework you like).

First lets create a Next JS project.


npx create-next-app meiliexample

Enter fullscreen mode Exit fullscreen mode

We will be using tailwind to style our search interface.

You can use the following guides to install tailwind css in your Next JS project.

https://www.freecodecamp.org/news/how-to-set-up-tailwind-css-with-next-js/

https://tailwindcss.com/docs/guides/nextjs

After this lets install libraries like axios to get the data from our express server to serve it in the frontend and install a library called lodash to use a function called debounce in JavaScript


npm i axios lodash

Enter fullscreen mode Exit fullscreen mode

Now we are going to connect with our Melli search API Routes and get the full text search content from the backend and show it to the user in the frontend with loading text placeholder before fetching the data. We are going to create a folder called API to call our Express JS routes which we made in the previous posts. So I will be calling my hosted url with help of axios and then use debounce from lodash to
"delay the execution of your code until the user stops performing a certain action", which means I will call the backend API only when the user has stopped typing in his input field.

I have also added code which you can use to add and delete your data points in melli search instance.

Before that I am initialising axios instance to be used in our app.


import axios from 'axios';

export const BASE_URL = 'yourapi.com';

export const instance = axios.create({
  baseURL: BASE_URL,
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
});


Enter fullscreen mode Exit fullscreen mode

Now comes the search feature,


import {instance} from './Api';

export const addResourceFromMelli = async melliSearchData => {
  const melliData = {
    index: 'movies',
    data: melliSearchData?.movieData,
  };

  try {
    await instance.post(`/mellisearch/add-document`, melliData);
  } catch (error) {
    console.log(error);
  }
};

export const searchResourcesFromMelli = async melliSearchData => {
  try {
    const melliData = {
      index: 'movies',
      search: melliSearchData?.search,
    };

    const res = await instance.post(`/mellisearch/search`, melliData);

    return res.data.hits;
  } catch (error) {
    console.log(error);

    return [];
  }
};

export const deleteResourceFromMelli = async melliSearchData => {
  const melliData = {
    index: 'movies',
    id: melliSearchData?.movieId,
  };

  try {
    await instance.post(`/mellisearch/delete-document`, melliData);
  } catch (error) {}
};


Enter fullscreen mode Exit fullscreen mode

Now, let us create a neat search interface and make the full text search feature with the melli search instance we have deployed.

We will be connecting the API with our frontend search field and introduce debounce as discussed before. So when ever user types, we will be calling the API for nice UX.

Below is the complete code.


import React, { useState, useCallback } from "react";
import { debounce } from "lodash";

import SearchMovies from "../../Assets/SearchMovies.png";
import Image from "next/image";
import { searchResourcesFromMelli } from "../../API/MellisearchAPI";


const SearchInterface = () => {
  const [query, setQuery] = useState("");
  const [moviesData, setMoviesData] = useState([]);
  const [searchLoader, setSearchLoader] = useState(false);

  const searchMelli = async (e) => {
    const melliSearch = {
      search: e,
    };

    const res = await searchResourcesFromMelli(melliSearch);
    setMoviesData(res);
    setSearchLoader(false);
  };

  const debouncedAPICall = useCallback(debounce(searchMelli, 1200), []);

  const debouncedFullTextSearch = (e) => {
    setSearchLoader(true);
    setQuery(e.target.value);
    debouncedAPICall(e.target.value);
  };

  return (
    <div>
      <div className="flex flex-col items-center justify-center">
        <div className="mt-5 ">
          <input
            value={query}
            onChange={debouncedFullTextSearch}
            placeholder="Search a movie..."
            className="border-1 border-blue-500 rounded-md px-2.5 py-3.5 bg-gray-100 text-black my-10 w-80 md:w-96"
          />
        </div>

        {searchLoader && (
          <div className="pt-7.5 mt-5 mb-7.5">
            <span>Loading...</span>
          </div>
        )}

        {moviesData?.length >= 1 ? (
          moviesData?.map((item) => (
            <div
              className="cursor-pointer my-4 shadow-md rounded-md w-11/12 md:w-96"
              key={item?.movieId}
            >
              <Image
                className=" w-full md:w-96 h-72 md:h-60 rounded-md"
                src={item.posterUrl}
                alt={item.title}
                width={400}
                height={400}
              />
              <div className="w-11/12 md:w-96 px-4">
                <div className="text-lg mb-1 leading-6.5 my-2">
                  {item.title}
                </div>
                <p className="text-base leading-6.5 mb-2.5">
                  {item.description}
                </p>
              </div>
            </div>
          ))
        ) : (
          <div className="flex flex-col items-center justify-center">
            {!searchLoader && (
              <div className="flex flex-col items-center justify-center">
                <Image
                  src={SearchMovies}
                  alt="Search Movies"
                  width={400}
                  height={400}
                />
                <p className="text-xl my-2.5">Start typing to search movies!</p>
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
};

export default SearchInterface;

Enter fullscreen mode Exit fullscreen mode

You can use this Search Interface as a Component or make it as a separate page in your next router as I have done in my demos website.

Thats it, we have successfully integrated a full stack full text search interface with React/Next JS and a Node JS Express Google Cloud functions backend.

We can scale this instance based on our requirement and as our user base increases.

You can view the complete code and see how the search interface works in a live demo below.

https://github.com/Gautham495/blog-code-snippets

https://demos.gauthamvijay.com/

Top comments (0)