Full text search with Firestore & Melli search - Implementing Full text search with MeiliSearch in React/Next JS
Tags: javascript, react, nextjs, firebase
Published: 2023-11-07 03:33:44

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.

```jsx

npx create-next-app meiliexample

```

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

```jsx

npm i axios lodash

```

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.

```jsx

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',
  },
});


```
Now comes the search feature,


```jsx

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) {}
};


```

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.

```jsx

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;

```

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/