Outline: [Article Title]

Keyword: [Enter Targeted Keyword]

Keyword MSV: [Enter Targeted Keyword’s Monthly Search Volume]

Author: [Enter Author Name]

Due Date: [Enter Due Date]

Publish Date: [Enter Desired Publish Date]

User Persona: [Enter Targeted Reader and/or User Persona]


It is very difficult to create search engines that are typo-tolerant, effective, and efficient. Even if the requested item is in the database, a typographical error may result in a search yielding no results. By removing the need to build a search engine from absolute scratch, Typesense could save a lot of time and effort. Furthermore, the users will be able to use the search feature in the application effectively, resulting in a positive user experience. Typesense is an open-source typo-tolerant search engine for developers that aims to reduce the time it takes to conduct effective and efficient searches. To learn more about typesense, follow this link. What is Typesense?

We’ll continue with our previous Booksearch typesense project and create a simple application in react.js for searching through a collection of ecommerce store products, again using typesense as the search engine. Everything will be written in javascript/typescript, and we’ll be using tailwind css to style our application from absolute scratch.

Let’s get started. The goal of this article is to create an instant search type application, also known as “search as you type,” which means that whenever you type something, the results appear instantly, providing a pleasant user experience. So, in a previous article, we created a simple Typesense Booksearch javascript application, and in this article, we’ll redo it but by using Ecommerce store dataset and by using react.js/next.js, with the goal of simply showing you how to do it using the most popular framework or UI library.So let’s get started with our Next js application with Typescript. To do so, simply follow the instructions below.

You can create a TypeScript Nextjs project with the –ts, –typescript flag:

npx create-next-app@latest --ts

# or

yarn create next-app --typescript

next js installation

Now, let’s use the following command to start the application.

yarn run dev

app running

Your application’s folder structure should look something like this.

Folder structure

let’s try to incorporate tailwind css into our application. To do so, follow the steps outlined below.The first step is to install the dependencies tailwindcss, postcss, and autoprefixer. So we’re going to install Tailwind CSS as a PostCSS plugin because that’s the easiest way to integrate it with build tools like webpack, Rollup, Vite, and Parcel.

npm install -D tailwindcss postcss autoprefixer

dependencies installation

Using npm or yarn, install tailwindcss and its dependencies, then create your tailwind.config.js file by using the command mentioned below.

npx tailwindcss init -p

config file

In your postcss.config.js file, or wherever PostCSS is configured in your project, add tailwindcss and autoprefixer and also inside your tailwind.config.js file, dont forget to add the paths to all of your template files.

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
// tailwind.config.js
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

Finally, in your ./styles/globals.css file, remove everything inside it and add the @tailwind directives for each of Tailwind’s layers.

@tailwind base;
@tailwind components;
@tailwind utilities;

So, before we go ahead and configure and install typesense into our application, let’s first style it and add the light and dark mode toggle features.Install the next-themes package in our application first, then go to our _app.tsx file and wrap the entire component with the ThemeProvider as shown in the code below.

import "../styles/globals.css";

import type { AppProps } from "next/app";
import { ThemeProvider } from "next-themes";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider attribute="class">
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

then go to the tailwind configuration file and replace the previously written code with the following code.

// tailwind.config.js
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  darkMode: false,
  theme: {
    extend: {
      keyframes: {
        wiggle: {
          "0%, 100%": { transform: "rotate(-3deg)" },
          "50%": { transform: "rotate(3deg)" },
        },
      },
      animation: {
        wiggle: "wiggle 200ms ease-in-out",
      },
    },
  },
  plugins: [],
};

Finally, remove the entire boilerplate code from the index.tsx file and replace it with the following code.

import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useTheme } from "next-themes";
import React, { useState } from "react";

const Home: NextPage = () => {
  const { theme, setTheme } = useTheme();
  const [wiggle, setWiggle] = useState(false);
  const [buttonText, setButtonText] = useState("Light☀️");
  const changeText = () =>
    setButtonText((text) => (text === "Light☀️" ? "Dark🌚" : "Light☀️"));

  return (
    <>
      <button
        className={`${
          wiggle && "animate-wiggle"
        } bg-green-500 p-4 text-black rounded-full hover:bg-green-400 hover:shadow-xl mt-5 ml-5`}
        onClick={() => {
          setTheme(theme === "dark" ? "light" : "dark");
          setWiggle(!wiggle);
          changeText();
        }}
        onAnimationEnd={() => setWiggle(false)}
      >
        {buttonText}
      </button>
      <div className={styles.container}>
        <main className={styles.main}>
          <h1 className={styles.title}>Ecommerce SearchApp Nextjs Typesense</h1>
        </main>
      </div>
    </>
  );
};

export default Home;

and then run your application by typing yarn run dev in the same project folder’s terminal, and you should see something like the image below.

Demo

Let’s get started with the typesense integration after we’ve successfully integrated tailwind into our application. First, let’s add some typesense instantsearch adapters to our application. We’ll also be adding another library created by algolia called “react-instantsearch-dom,” which provides widgets and components for react to use with the instantsearch library, let’s add those libraries to our application.So, to add those libraries to our application, simply run the command below in the terminal of your root project file directory.

npm install typesense-instantsearch-adapter @types/react-instantsearch-dom

Dependencies installation

Typesense Setup

The simplest way to get started with Typesense is to install its docker image or use the Typesense cloud hosting solution. To get started, go to the Typesense cloud website and sign up using your Github account, or use the docker method directly. We’ll use the Docker method for the purposes of this tutorial. To do so, go to Typesense Dockerhub and download the prebuilt docker image, then follow the steps outlined below.

docker pull typesense/typesense

and

mkdir /tmp/typesense-data
docker run -p 8108:8108 -v/tmp/data:/data typesense/typesense:0.22.2 --data-dir /data --api-key=ecommerce --enable-cors

Docker running

Docker

We can now finally adjust the project to use Typesense.We’ve got our typesense instance up and running in the background.To get Next.js to use the Typesense adapter, open pages/index.tsx and create a connection. Inside that, create a TypesenseInstantsearchAdapter object and add server as a key. Inside that, pass the apiKey and nodes, and inside the nodes, specify the host, port, and protocol. Finally, add the additional search parameter to it and pass the query and queryByWeight that you want to display the indexed document/data accordingly.Remember that these parameters are passed directly to the Typesense search API endpoint. As a result, any parameters supported by the search endpoint can be passed.

The following is an example of what your code should look like.

// pages/index.tsx
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useTheme } from "next-themes";
import React, { useState } from "react";
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";

const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "ecommerce",
    nodes: [
      {
        host: "localhost",
        port: "8108",
        protocol: "http",
      },
    ],
  },
  additionalSearchParameters: {
    queryBy: "name,categories,description",
    queryByWeights: "4,2,1",
    numTypos: 1,
    typoTokensThreshold: 1,
  },
});

const Home: NextPage = () => {
  const { theme, setTheme } = useTheme();
  const [wiggle, setWiggle] = useState(false);
  const [buttonText, setButtonText] = useState("Light☀️");
  const changeText = () =>
    setButtonText((text) => (text === "Light☀️" ? "Dark🌚" : "Light☀️"));

  return (
    <>
      <button
        className={`${
          wiggle && "animate-wiggle"
        } bg-green-500 p-4 text-black rounded-full hover:bg-green-400 hover:shadow-xl mt-5 ml-5`}
        onClick={() => {
          setTheme(theme === "dark" ? "light" : "dark");
          setWiggle(!wiggle);
          changeText();
        }}
        onAnimationEnd={() => setWiggle(false)}
      >
        {buttonText}
      </button>
      <div className={styles.container}>
        <main className={styles.main}>
          <h1 className={styles.title}>Ecommerce SearchApp Nextjs Typesense</h1>
        </main>
      </div>
    </>
  );
};

export default Home;

Now that we’ve completed the configuration, let’s move on to creating an interface for our application. To do so, first import the InstantSearch component from the react-instantsearch-dom library, and pass indexName and searchClient as props to that component.

// pages/index.ts
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useTheme } from "next-themes";
import React, { useState } from "react";
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
import { InstantSearch } from "react-instantsearch-dom";

const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "ecommerce",
    nodes: [
      {
        host: "localhost",
        port: "8108",
        protocol: "http",
      },
    ],
  },
  additionalSearchParameters: {
    queryBy: "name,categories,description",
    queryByWeights: "4,2,1",
    numTypos: 1,
    typoTokensThreshold: 1,
  },
});

const Home: NextPage = () => {
  const { theme, setTheme } = useTheme();
  const [wiggle, setWiggle] = useState(false);
  const [buttonText, setButtonText] = useState("Light☀️");
  const changeText = () =>
    setButtonText((text) => (text === "Light☀️" ? "Dark🌚" : "Light☀️"));

  return (
    <>
      <h1 className={styles.title}>Ecommerce Search App Nextjs Typesense</h1>
      <button
        className={`${
          wiggle && "animate-wiggle"
        } bg-green-500 p-4 text-black rounded-full hover:bg-green-400 hover:shadow-xl mt-5 ml-5`}
        onClick={() => {
          setTheme(theme === "dark" ? "light" : "dark");
          setWiggle(!wiggle);
          changeText();
        }}
        onAnimationEnd={() => setWiggle(false)}
      >
        {buttonText}
      </button>

      <InstantSearch
        indexName="products"
        searchClient={typesenseInstantsearchAdapter.searchClient}
      ></InstantSearch>
    </>
  );
};

export default Home;

It should look something like this when you spin up your next.js application.

Demo

Now we need to import our products data, so create a dataset folder and inside it, create your own ecommerce products json file, filling it with all of the necessary product information, or download the ecommerce dataset from here. Finally, your folder structure should look something like this.

folder structure

Let’s get started writing the data-importing scripts. We’ll start by creating a file called loadData.js in which we’ll initialize the typesense client.

//loadData.js
const Typesense = require("typesense");

const runClient = () => {
  let client = new Typesense.Client({
    nodes: [
      {
        host: "localhost", // For Typesense Cloud use xxx.a1.typesense.net
        port: "8108", // For Typesense Cloud use 443
        protocol: "http", // For Typesense Cloud use https
      },
    ],
    apiKey: "ecommerce",
    connectionTimeoutSeconds: 2,
  });
};

runClient();

Creating a collection named books

A Collection in Typesense is a set of related Documents that functions similarly to a table in a relational database. We give a collection a name and describe the fields that will be indexed when a document is added to the collection when we create it.

Now create a productSchema, and finally create a script to manage the bulk import capabilities/functionality so head over to the loadData.js file and make the following changes to the code.

const Typesense = require("typesense");
const fs = require("fs/promises");

const runClient = async () => {
  let client = new Typesense.Client({
    nodes: [
      {
        host: "localhost",
        port: "8108",
        protocol: "http",
      },
    ],
    apiKey: "ecommerce",
    connectionTimeoutSeconds: 2,
  });

  const productsSchema = {
    name: "products",
    num_documents: 0,
    fields: [
      {
        name: "name",
        type: "string",
        facet: false,
      },
      {
        name: "description",
        type: "string",
        facet: false,
      },
      {
        name: "brand",
        type: "string",
        facet: true,
      },
      {
        name: "categories",
        type: "string[]",
        facet: true,
      },
      {
        name: "categories.lvl0",
        type: "string[]",
        facet: true,
      },
      {
        name: "categories.lvl1",
        type: "string[]",
        facet: true,
        optional: true,
      },
      {
        name: "categories.lvl2",
        type: "string[]",
        facet: true,
        optional: true,
      },
      {
        name: "categories.lvl3",
        type: "string[]",
        facet: true,
        optional: true,
      },
      {
        name: "price",
        type: "float",
        facet: true,
      },
      {
        name: "image",
        type: "string",
        facet: false,
      },
      {
        name: "popularity",
        type: "int32",
        facet: false,
      },
      {
        name: "free_shipping",
        type: "bool",
        facet: true,
      },
      {
        name: "rating",
        type: "int32",
        facet: true,
      },
    ],
    default_sorting_field: "popularity",
  };

  console.log("Populating index in Typesense");

  const products = require("./dataset/ecommerce.json");

  let reindexNeeded = false;
  try {
    const collection = await client.collections("products").retrieve();
    console.log("Found existing schema");
    if (
      collection.num_documents !== products.length ||
      process.env.FORCE_REINDEX === "true"
    ) {
      console.log("Deleting existing schema");
      reindexNeeded = true;
      await client.collections("products").delete();
    }
  } catch (e) {
    reindexNeeded = true;
  }

  if (!reindexNeeded) {
    return true;
  }

  console.log("Creating schema: ");
  console.log(JSON.stringify(productsSchema, null, 2));
  await client.collections().create(productsSchema);

  console.log("Adding records: ");

  // Bulk Import
  products.forEach((product) => {
    product.free_shipping = product.name.length % 2 === 1;
    product.rating = (product.description.length % 5) + 1;
    product.categories.forEach((category, index) => {
      product[`categories.lvl${index}`] = [
        product.categories.slice(0, index + 1).join(" > "),
      ];
    });
  });

  try {
    const returnData = await client
      .collections("products")
      .documents()
      .import(products);
    console.log(returnData);
    console.log("Done indexing.");

    const failedItems = returnData.filter((item) => item.success === false);
    if (failedItems.length > 0) {
      throw new Error(
        `Error indexing items ${JSON.stringify(failedItems, null, 2)}`
      );
    }

    return returnData;
  } catch (error) {
    console.log(error);
  }
};

runClient();

Finally, type node loadData.js into the terminal of that same project directory, and you should see something similar to the image below.

populate index populate index

Before we dive into integrating searchbox, let’s style our application and segregate the search section, as well as add some style to our interface, so simply follow the code below and wrap it inside the InstantSearch component.

<div className="flex">
  <aside className="w-1/4 h-screen"></aside>
  <main>Search/result section</main>
</div>

Now let’s add the SearchBox and Hits components from the react-instantsearch-dom library, so we can directly incorporate those components inside our application.

import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useTheme } from "next-themes";
import React, { useState } from "react";
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
import { InstantSearch, SearchBox, Hits } from "react-instantsearch-dom";

const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "ecommerce",
    nodes: [
      {
        host: "localhost",
        port: "8108",
        protocol: "http",
      },
    ],
  },
  additionalSearchParameters: {
    queryBy: "name,categories,description",
    queryByWeights: "4,2,1",
    numTypos: 1,
    typoTokensThreshold: 1,
  },
});

const Home: NextPage = () => {
  const { theme, setTheme } = useTheme();
  const [wiggle, setWiggle] = useState(false);
  const [buttonText, setButtonText] = useState("Light☀️");
  const changeText = () =>
    setButtonText((text) => (text === "Light☀️" ? "Dark🌚" : "Light☀️"));

  return (
    <>
      <h1 className={styles.title}>Ecommerce Search App Nextjs Typesense</h1>
      <button
        className={`${
          wiggle && "animate-wiggle"
        } bg-green-500 p-4 text-black rounded-full hover:bg-green-400 hover:shadow-xl mt-5 ml-5`}
        onClick={() => {
          setTheme(theme === "dark" ? "light" : "dark");
          setWiggle(!wiggle);
          changeText();
        }}
        onAnimationEnd={() => setWiggle(false)}
      >
        {buttonText}
      </button>

      <InstantSearch
        indexName="products"
        searchClient={typesenseInstantsearchAdapter.searchClient}
      >
        <div className="flex">
          <aside className="w-1/4 h-screen"></aside>
          <main>
            <SearchBox />
            <Hits />
          </main>
        </div>
      </InstantSearch>
    </>
  );
};

export default Home;

Simply re-run the application after you’ve fixed it, and your application should now look like this.

Demo

At the current state our data is currently displayed in json format, as it is in our index.Let’s present the data in a more appealing manner, so let’s create a new component and call it ResultTemplate, pass the hit props to that component, and create an interface for that hit with any type.Finally, display the name of the products by simply passing {hit.name} and wrapping it with div.

interface Props {
  hit: any;
}

export const ResultTemplate = ({ hit }: Props) => {
  return (
    <>
      <div>{hit.name}</div>
    </>
  );
};

The final code for the index.tsx file should look like this.

import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useTheme } from "next-themes";
import React, { useState } from "react";
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
import { InstantSearch, SearchBox, Hits } from "react-instantsearch-dom";

const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "ecommerce",
    nodes: [
      {
        host: "localhost",
        port: "8108",
        protocol: "http",
      },
    ],
  },
  additionalSearchParameters: {
    queryBy: "name,categories,description",
    queryByWeights: "4,2,1",
    numTypos: 1,
    typoTokensThreshold: 1,
  },
});

interface Props {
  hit: any;
}

export const ResultTemplate = ({ hit }: Props) => {
  return (
    <>
      <div>{hit.name}</div>
    </>
  );
};

const Home: NextPage = () => {
  const { theme, setTheme } = useTheme();
  const [wiggle, setWiggle] = useState(false);
  const [buttonText, setButtonText] = useState("Light☀️");
  const changeText = () =>
    setButtonText((text) => (text === "Light☀️" ? "Dark🌚" : "Light☀️"));

  return (
    <>
      <h1 className={styles.title}>Ecommerce Search App Nextjs Typesense</h1>
      <button
        className={`${
          wiggle && "animate-wiggle"
        } bg-green-500 p-4 text-black rounded-full hover:bg-green-400 hover:shadow-xl mt-5 ml-5`}
        onClick={() => {
          setTheme(theme === "dark" ? "light" : "dark");
          setWiggle(!wiggle);
          changeText();
        }}
        onAnimationEnd={() => setWiggle(false)}
      >
        {buttonText}
      </button>

      <InstantSearch
        indexName="products"
        searchClient={typesenseInstantsearchAdapter.searchClient}
      >
        <div className="flex">
          <aside className="w-1/4 h-screen"></aside>
          <main>
            <SearchBox />
            <Hits hitComponent={ResultTemplate} />
          </main>
        </div>
      </InstantSearch>
    </>
  );
};

export default Home;

After you’ve fixed it, simply re-run the application, and it should now look like this.

Demo

So, let’s make them reults display in a grid format by adding some styling to our app, so go to the application’s main page and inspect the element there.

Inspect element

So the concept here with this react instance search library is that those elements have predefined classes names, as you can see each element has an ais-Hits,ais-Hits-lists and then you have ais-Hits-items. so we just need to change the styles and because we’re using tailwind, we’ll use the “@apply” with a grid of four columns and let’s save the application and simply re-run the it.

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  /* Your default theme */
  --background: white;
  --foreground: black;
}

[data-theme="dark"] {
  --background: black;
  --foreground: white;
}

.ais-Hits-list {
  @apply grid grid-cols-4 gap-4;
}

Also, don’t forget to make some changes to the ResultTemplate before re-running the application.

export const ResultTemplate = ({ hit }: Props) => {
  return (
    <>
      <div className="bg-gray-500 shadow-md rounded-lg p-9">
        <h3 className="truncate">{hit.name}</h3>
      </div>
    </>
  );
};

and the application should now look something like this.

Demo

It’s time to display some images of our ecommerce product, so we’ll use the “Image component” that “nextjs” provides and simply pass”hit.image” as an image source, followed by the image’s dimensions.

export const ResultTemplate = ({ hit }: Props) => {
  return (
    <>
      <div className="bg-gray-500 shadow-md rounded-lg p-9 mr-5">
        <div className="p-24">
          <Image src={hit.image} height={160} width={85} layout="responsive" />
        </div>
        <h3 className="truncate">{hit.name}</h3>
      </div>
    </>
  );
};

If you run the application, you will almost certainly get this error.

error

This error occurs because these images are served from a different host, so we must add those domains to our configuration file. To do so, go to the next.config.js file in the project directory’s root directory and update the image domains, then restart the entire application.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: ["cdn-demo.algolia.com"],
  },
};

module.exports = nextConfig;

Hence, your application should look something like this. Demo

It is time to configure the list of hits that you want to display in your page, so simply add the Configure component and specify the hitsPerPage parameter. Finally, let’s add pagination, so simply use the Pagination widgets provided by the react-instantsearch-dom library.Also,let’s try to add the facets ,to do that so we’ll add a widgets and in react-instanctsearch-dom library this is called RefinementList and then we have to specify the attribute we want to take so in our case let’s call it brand and re-run our application it should probaly work. As a result, your final code should resemble something like this…

// index.tsx
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useTheme } from "next-themes";
import React, { useState } from "react";
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
import {
  InstantSearch,
  SearchBox,
  Hits,
  Configure,
  Pagination,
  RefinementList,
} from "react-instantsearch-dom";

const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "ecommerce",
    nodes: [
      {
        host: "localhost",
        port: "8108",
        protocol: "http",
      },
    ],
  },
  additionalSearchParameters: {
    queryBy: "name,categories,description",
    queryByWeights: "4,2,1",
    numTypos: 1,
    typoTokensThreshold: 1,
  },
});

interface Props {
  hit: any;
}

export const ResultTemplate = ({ hit }: Props) => {
  return (
    <>
      <div className="bg-gray-500 shadow-md rounded-lg p-9 mr-5">
        <div className="p-24">
          <Image src={hit.image} height={160} width={85} layout="responsive" />
        </div>
        <h3 className="truncate">{hit.name}</h3>
      </div>
    </>
  );
};

const Home: NextPage = () => {
  const { theme, setTheme } = useTheme();
  const [wiggle, setWiggle] = useState(false);
  const [buttonText, setButtonText] = useState("Light☀️");
  const changeText = () =>
    setButtonText((text) => (text === "Light☀️" ? "Dark🌚" : "Light☀️"));

  return (
    <>
      <h1 className={styles.title}>Ecommerce Search App Nextjs Typesense</h1>
      <button
        className={`${
          wiggle && "animate-wiggle"
        } bg-green-500 p-4 text-black rounded-full hover:bg-green-400 hover:shadow-xl mt-5 ml-5`}
        onClick={() => {
          setTheme(theme === "dark" ? "light" : "dark");
          setWiggle(!wiggle);
          changeText();
        }}
        onAnimationEnd={() => setWiggle(false)}
      >
        {buttonText}
      </button>

      <InstantSearch
        indexName="products"
        searchClient={typesenseInstantsearchAdapter.searchClient}
      >
        <Configure hitsPerPage={12} />
        <div className="flex">
          <aside className="w-1/4 pl-4 h-screen">
            <RefinementList attribute="brand" />
          </aside>
          <main>
            <SearchBox />
            <Hits hitComponent={ResultTemplate} />
            <Pagination />
          </main>
        </div>
      </InstantSearch>
    </>
  );
};

export default Home;

and you application should look like this:

Demo

Finally, we can add sorting functionality to the application by following the same steps as before: add the widget/component called SortBy from react-instantsearch-dom, and specify the items with the label default with the value products, and then create another label called Price (asc) with the value products/sort/price:asc, and again another label called Price (desc) with the value products/sort/price:desc.

<SortBy
  items={[
    { label: "Default", value: "products" },
    { label: "Price (asc)", value: "products/sort/price:asc" },
    { label: "Price (desc)", value: "products/sort/price:desc" },
  ]}
  defaultRefinement="products"
/>

Ultimately, as a final step, let’s update the template for the information that we want to display(for example: price and description) in our app, so here’s how your code should look.

// pages/index.tsx
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useTheme } from "next-themes";
import React, { useState } from "react";
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
import {
  InstantSearch,
  SearchBox,
  Hits,
  Configure,
  Pagination,
  SortBy,
  RefinementList,
} from "react-instantsearch-dom";

const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "ecommerce",
    nodes: [
      {
        host: "localhost",
        port: "8108",
        protocol: "http",
      },
    ],
  },
  additionalSearchParameters: {
    queryBy: "name,categories,description",
    queryByWeights: "4,2,1",
    numTypos: 1,
    typoTokensThreshold: 1,
  },
});

interface Props {
  hit: any;
}

export const ResultTemplate = ({ hit }: Props) => {
  return (
    <>
      <div className="bg-gray-500 shadow-md rounded-lg p-9 mr-5">
        <div className="p-24">
          <Image src={hit.image} height={160} width={85} layout="responsive" />
        </div>
        <h3 className="truncate">{hit.name}</h3>
        <h3 className="truncate">Price: ${hit.price}</h3>
        <p>{hit.description}</p>
      </div>
    </>
  );
};

const Home: NextPage = () => {
  const { theme, setTheme } = useTheme();
  const [wiggle, setWiggle] = useState(false);
  const [buttonText, setButtonText] = useState("Light☀️");
  const changeText = () =>
    setButtonText((text) => (text === "Light☀️" ? "Dark🌚" : "Light☀️"));

  return (
    <>
      <h1 className={styles.title}>Ecommerce Search App Nextjs Typesense</h1>
      <button
        className={`${
          wiggle && "animate-wiggle"
        } bg-green-500 p-4 text-black rounded-full hover:bg-green-400 hover:shadow-xl mt-5 ml-5`}
        onClick={() => {
          setTheme(theme === "dark" ? "light" : "dark");
          setWiggle(!wiggle);
          changeText();
        }}
        onAnimationEnd={() => setWiggle(false)}
      >
        {buttonText}
      </button>

      <InstantSearch
        indexName="products"
        searchClient={typesenseInstantsearchAdapter.searchClient}
      >
        <Configure hitsPerPage={12} />
        <div className="flex">
          <aside className="w-1/4 pl-4 h-screen">
            <RefinementList attribute="brand" />
          </aside>
          <main>
            <SearchBox />
            <SortBy
              items={[
                { label: "Relevancy", value: "products" },
                { label: "Price (asc)", value: "products/sort/price:asc" },
                { label: "Price (desc)", value: "products/sort/price:desc" },
              ]}
              defaultRefinement="products"
            />
            <Hits hitComponent={ResultTemplate} />
            <Pagination />
          </main>
        </div>
      </InstantSearch>
    </>
  );
};

export default Home;

Let’s take a look at the final version of our typesense-integrated Nextjs Ecommerce Search application.

Demo

Entire source code of the application can be found here

Closing

Typesense was built with several distinctive features primarily aimed at making the developer’s job easier while also giving customer as well as user the ability to provide a better search experience as possible.This article may have been entertaining as well as instructive in terms of how to install typesense from the ground up on a variety of platforms. Join Aviyel’s community to learn more about the open source project, get tips on how to contribute, and join active dev groups.

Call-to-Action

Aviyel is a collaborative platform that assists open source project communities in monetizing and long-term sustainability. To know more visit Aviyel.com and find great blogs and events, just like this one! Sign up now for early access, and don’t forget to follow us on our socials!