Drastically improve your users' product discovery experience with our easy-to-use and easy-to-deploy search engine.
Introduction
Searching is a crucial feature of any e-commerce application which directly affects conversions. A good search experience requires quick and accurate search results. However, it requires investing time and developer resources to build. Here's when Meilisearch enters the picture—providing an open-source search engine that is lightning fast, hyper-relevant, and typo-tolerant with little to no setup time.
In this tutorial, we'll add a seamless search experience with Meilisearch to a light e-commerce application. We will import a list of 1,000 products to Meilisearch. Users can search through these products and benefit from advanced features like filtering and sorting options.
Upon completion, you will have an app similar to our demo application at http://ecommerce.meilisearch.com, which provides rapid search results on a catalog of over 1.4 million products.
*Prerequisites*
- NodeJs
- Next.js - React Framework
- Meilisearch
Getting Started
Let's get started with the installation of the necessary tools.
1. *Install and launch Meilisearch*
You have many ways to install Meilisearch. One of the methods is to use cURL for the installation.
You can paste the following code into your terminal:
# Install Meilisearch
curl -L https://install.meilisearch.com | sh
# Launch Meilisearch
./meilisearch
This command will launch a local Meilisearch server at http://localhost:7700/
.
2. Adding data to Meilisearch
Create and navigate to a new folder called seed
. You can use the following command to install Meilisearch's Javascript client using npm.
npm install meilisearch
We have gathered a list of 1,000 products from various Amazon datasets and compiled them in a data.json
file. We will add these products to the Meilisearch index.
You can download this data.json
file from GitHub.
Each record is associated with a product. Each product has a brand, category, tag, price, rating, and other related information. We will make these attributes sortable and filterable in our Meilisearch instance via [updateFilterableAttributes](https://docs.meilisearch.com/reference/api/filterable_attributes.html#update-filterable-attributes)
and [updateSortableAttributes](https://docs.meilisearch.com/reference/api/sortable_attributes.html#update-sortable-attributes)
methods.
/* Here's an example of a product record in the data.json file */
[
{
"id": "711decb2a3fdcbbe44755afc5af25e2f",
"title": "Kitchenex Stainless Steel Flatware Pie Server and Pie Cutter Set of 2",
"description": "The Kitchen Stainless Pie Server is both flashy and chic...",
"category": "Home & Kitchen",
"brand": "Dr. Pet",
"price": 16.84,
"tag": "Kitchen",
"rating": 4.7,
"reviews_count": 7
},
...999 more items
]
Let’s create a script to add the data from the data.json
file to a products
index in Meilisearch. Here’s the code of the script:
const { MeiliSearch } = require("meilisearch");
const client = new MeiliSearch({
host: "http://localhost:7700",
apiKey: "", // No API key has been set
});
const INDEX_NAME = "products";
const index = client.index(INDEX_NAME);
const data = require("./data.json");
(async () => {
console.log(`Adding Filterable and Sortable Attributes to "${INDEX_NAME}"`);
await index.updateFilterableAttributes([
"brand",
"category",
"tag",
"rating",
"reviews_count",
"price",
]);
await index.updateSortableAttributes(["reviews_count", "rating", "price"]);
console.log(`Adding Documents to "${INDEX_NAME}"`);
await index.updateDocuments(data);
console.log('Documents Added');
})();
If you called your script seed.js
, you could run the command below to start adding data to your Meilisearch instance:
node seed.js
Wait until the data is ingested into the Meilisearch instance. It is usually done in a few seconds. You may go to [http://localhost:7700/](http://localhost:7700/)
to check the list of 1000 products in the Meilisearch.
3. Set up the project
Let's build an e-commerce application in Next.js. N*ext.js* is an open-source React framework that enables server-side rendering and static website generation.
We can set up a Next.js application using the create-next-app tool. Navigate to your project base directory in the terminal, and use the following command to install a Next.js application:
npx create-next-app@latest ecommerce
It may take a few minutes to complete. This command will create a folder named ecommerce
in your project base directory with all the boilerplate code. Navigate to the ecommerce
folder and install the following npm libraries:
-
react-instantsearch-dom
- an open-source library that uses InstantSearch.js to build search interfaces in front-end applications quickly. -
@meilisearch/instant-meilisearch
- It's a client forInstantSearch.js
. It establishes communication between Meilisearch and InstantSearch.
You can install these libraries using the following command:
npm i --save react-instantsearch-dom @meilisearch/instant-meilisearch
You may add some pre-defined stylesheet files. You can download all the CSS modules from here and paste all the files into the styles
folder in the base directory.
Important Note:
As of Next.js v12 and above, react-instantsearch-dom
is not working as expected when React’s Strict mode is enabled (refer to link). A quick and easy fix for this is to disable strict mode. You can disable it by adding reactStrictMode: false
to next.config.js
. You may find this file at the root of your project.
/* next.config.js */
module.exports = {
reactStrictMode: false, // set this to false
images: {
domains: [''],
},
}
To apply modifications, restart the Next.js server.
4. Building and integrating components
We have completed the initial step of seeding the data to the Meilisearch instance and installing the necessary tools. From here, we will start integrating the components required for creating a search experience in the application.
4.1 Adding search box and connecting components to Meilisearch
We can create a navigation bar that contains a search box to perform searches within the application. We will use the SearchBox
component from the react-instantsearch-dom
library to do so.
You can create a navbar.jsx
file in the components/layout
folder and use the following code in it:
import React from 'react';
import { SearchBox } from 'react-instantsearch-dom';
import styles from '../../styles/nav-bar.module.css';
export const NavBar = () => {
return (
<div className={styles.container}>
<SearchBox />
</div>
);
};
export default NavBar;
We must connect the SearchBox
component to Meilisearch. We will create a searchClient
with the necessary credentials and pass it to the InstantSearch
component as a prop along with the index name.
To do this, you can create a layout.jsx
file in the components/layout/
folder and use the following code in it:
import React from 'react';
import NavBar from './navbar';
import { InstantSearch } from 'react-instantsearch-dom';
import { instantMeiliSearch } from '@meilisearch/instant-meilisearch';
const searchClient = instantMeiliSearch('http://localhost:7700', '');
const Layout = ({ children }) => {
return (
<InstantSearch indexName="products" searchClient={searchClient}>
<NavBar />
<main>{children}</main>
</InstantSearch>
);
};
export default Layout;
Next.js
uses the App component to initialize pages. Every time the server is launched, _app.js
is the primary file rendered. We will add the Layout
component and a few stylesheet files to the _app.js
file. Adding the Layout
component will supply the search results to all the child components.
You may replace the code in the _app.js
file with the following code:
import Layout from "../components/layout/layout";
import "../styles/globals.css";
import "../styles/searchBoxAIS.css";
function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
export default MyApp;
4.2 Creating various search filters
Let’s add a filter functionality to the application. We will need a few components from the react-instantsearch-dom
library:
-
The
RefinementList
component can be used to filter the data based on facets. The number of items related to the given filter will also be displayed.
<RefinementList attribute="category" />
-
The
RatingMenu
component creates a rating list. We need to define what’s the maximum rating. It displays a select menu (starting from 1 star till max) to select the rating. The component only works with integers.
<RatingMenu attribute="rating" max={5} />
-
The
ClearRefinements
component will be used to provide a button that will clear all the applied filters within the application. This is handy when we've applied many filters and want to delete them all at once.
<ClearRefinements />
We will add all three components to implement the filter functionality. You can create a new file SearchFilters.jsx
in the components/home/
folder and use the following code:
import {
ClearRefinements,
RatingMenu,
RefinementList,
} from 'react-instantsearch-dom';
const SearchFilters = () => (
<div>
<h2>
<span>Filters</span>
<ClearRefinements />
</h2>
<h4>Categories</h4>
<RefinementList attribute="category" />
<h4>Tags</h4>
<RefinementList attribute="tag" />
<h4>Brands</h4>
<RefinementList attribute="brand" />
<h4>Rating</h4>
<RatingMenu attribute="rating" max={5} />
</div>
);
export default SearchFilters;
4.3 Implementing a product card component
We will create a component named Hit
to represent a single product. We may design a card view for the component that will include the basic information for the product, like - title, description, rating, review count, price, brand, and image. All these details can be retrieved from the component’s product
prop. After that, we can display the results by looping over the component.
We can create a file hits.jsx
in the components/home/
folder. You can paste the following lines of code into the file:
import styles from "../../styles/searchResult.module.css";
const Hit = ({ product }) => {
const { rating, images, title, description, brand, price, reviews_count } = product;
return (
<div className={styles.card}>
<div
className={styles.productResImg}
style={{ backgroundImage: `url(${images[0]})` }}
/>
<div className={styles.productResCntnt}>
<h6 className={styles.productResBrand}>{brand}</h6>
<div className={styles.productResTitl}>{title}</div>
<div className={styles.productResDesc}>
{description.substring(0, 50)}...
</div>
<div className={styles.productResPrice}>${price}</div>
<div className={styles.productResReview}>
{reviews_count ? (
<div className={styles.productRateWrap}>
<span className={styles.productRate}>
{reviews_count} review
</span>{" "}
<span>⭐ {rating}</span>
</div>
) : (
"No Review"
)}
</div>
</div>
</div>
);
};
export default Hit;
4.4 Designing a page layout to display the list of products
It’s time to design the product list page with a sort feature and pagination to display a limited number of products on a page. For this, we can use the following components from the react-instantsearch-dom
library:
-
The
Pagination
component, by default, allows you to display a list of 20 products per page. We can also provide theshowLast
property to display an estimate of the number of pages.
<Pagination showLast={true} />
-
The
SortBy
component will sort the search results based on the facets. We need to provide a Meilisearch index and a list ofvalue
andlabel
.Let's say we need to sort the
category
in the indexproducts
alphabetically. You may use the following code:
<SortBy defaultRefinement="products" items={[ { value: 'category', label: 'Sort category alphabetically' }, ]} />
The
SortBy
has a few properties, including thedefaultRefinement
attribute for providing the Meilisearch index and theitems
attribute to provide a list ofvalue
andlabel
.The
value
includes the document's attribute, and as the name suggests, we will use thelabel
to add label text in a dropdown list. -
The
connectStateResults
hook will retrieve the search results from the Meilisearch instance usingInstantSearch
. TheconnectStateResults
function has some arguments, including:-
searchState
provides the information about the user's input in the provided search box. -
searchResults
gives the result obtained after querying the Meilisearch instance. - The
searching
argument represents the loading state for the result retrieval from the Meilisearch.
-
We will use the searchState
argument to get the list of products and loop the list using the map
function over the Hit
component to display the result. We will use the SortBy
component to sort based on price
and reviews_count
.
We can create a file SearchResult.jsx
in the component/home/
folder and use the following code:
import {
Pagination,
SortBy,
connectStateResults,
} from 'react-instantsearch-dom';
import SearchFilters from './SearchFilters';
import Hit from "./hits.jsx";
import styles from '../../styles/searchResult.module.css';
const Results = connectStateResults(
({ searchState, searchResults, searching }) => {
const hits = searchResults?.hits;
if (searching) {
// This will show the loading state till the results are retrieved
return 'Loading';
}
return (
<>
<SearchFilters />
<div className={styles.products}>
{/* Condition for rendering component based on results */}
{searchResults?.nbHits !== 0 ? (
<>
<div className={styles.resultPara}>
<span>
Showing {searchResults?.hits.length || 0} of{' '}
{searchResults?.nbHits || 0}{' '}
{searchState.query &&
!searching &&
`for "${searchState.query}"`}
</span>
<div>
<SortBy
defaultRefinement="products"
items={[
{ value: 'products', label: 'Sort' },
{
value: 'products:price:desc',
label: 'Price: High to Low',
},
{
value: 'products:price:asc',
label: 'Price: Low to High',
},
{
value: 'products:reviews_count:desc',
label: 'Most Reviews',
},
]}
/>
</div>
</div>
<div className={styles.grid}>
{/* Using search result list, we will loop over the Hit component */}
{hits?.length > 0 &&
hits.map((product) => (
<Hit key={product.id} product={product} />
))}
</div>
</>
) : (
<p className={styles.paragraph}>
No results have been found for {searchState.query}.
</p>
)}
{/* Adding pagination over the results */}
<Pagination showLast={true} />
</div>
</>
);
}
);
export default Results;
4.5 Displaying results on the application
We have created all the required components to display the results. We need to add the Results
component to the index.jsx
file to display the result on the screen. You can copy and paste the following code into the index.jsx
file.
import styles from '../styles/home.module.css'
import Results from '../components/home/SearchResult'
export default function Home () {
return (
<div className={styles.container}>
<main className={styles.main}>
<div className={styles.mainContent}>
<Results />
</div>
</main>
</div>
)
}
export const getStaticProps = async () => {
return {
props: {}
}
}
You can use the following command to run the application:
npm run dev
Go to http://localhost:3000. The output will resemble the image provided below:
You can find the complete code here, https://github.com/shivaylamba/demos/tree/main/src/ecommerce.
Conclusion
We created a lightning-fast search experience for an e-commerce application. You can now take the learnings from above and integrate a similar search experience into your applications. You may play with typo-tolerance, geo-search filters, and many other features to better suit your needs.
Here are some references of real-world examples of Meilisearch used in e-commerce experiences:
We're excited to see what you come up with. Share your experience and Meilisearch integration in your e-commerce application on the Slack community.
If you have any queries or suggestions, please let us know on Slack. For more information on Meilisearch, check out our Github repository and official documentation.”
Top comments (0)