Product search with inventory
How to use Optimizely Graph to search and display products by keyword and price range.
You can use Optimizely Graph queries to search products by keyword, price range, and inventory status. You can test queries in the GraphQL Explorer.
Basic product search query
Use this query to search for products by keyword and filter them by price. It helps you build ecommerce experiences using the GenericProduct model.
The following query searches for products that match a keyword in their name or description and filters results by a price range, and returns the most relevant products first:
query ProductSearchWithInventory($searchTerm: String, $minPrice: Float, $maxPrice: Float, $skip: Int = 0, $limit: Int = 20) {
GenericProduct(
where: {
_and: [
{
_or: [
{ _fulltext: { match: $searchTerm } }
{ Name: { match: $searchTerm, boost: 5 } }
{ Description: { match: $searchTerm, boost: 3 } }
]
}
{ DefaultMarketPrice: { gte: $minPrice, lte: $maxPrice } }
]
}
skip: $skip
limit: $limit
orderBy: { _ranking: SEMANTIC }
) {
total
facets {
DefaultMarketPrice(
ranges: [
{ from: 0, to: 50 }
{ from: 50, to: 100 }
{ from: 100, to: 500 }
{ from: 500 }
]
) {
name
count
}
}
items {
_score
Name
RelativePath
Description
DefaultMarketPrice
DefaultImageUrl
}
}
}Advanced product search with inventory status
Use this query to refine your product search with boosted relevance and sorted pricing. It lets you sort available products in ascending order by price.
This query boosts matches in product names and descriptions and filters by price, and sorts results by relevance and price (lowest first):
query AdvancedProductSearch(
$searchTerm: String,
$minPrice: Float,
$maxPrice: Float,
$skip: Int = 0,
$limit: Int = 20
) {
GenericProduct(
where: {
_and: [
{
_or: [
{ _fulltext: { match: $searchTerm } }
{ Name: { match: $searchTerm, boost: 8 } }
{ Description: { match: $searchTerm, boost: 3 } }
]
}
{ DefaultMarketPrice: { gte: $minPrice, lte: $maxPrice } }
]
}
skip: $skip
limit: $limit
orderBy: { _ranking: SEMANTIC, DefaultMarketPrice: ASC }
) {
total
facets {
DefaultMarketPrice(ranges: [
{ from: 0, to: 50 }
{ from: 50, to: 100 }
{ from: 100, to: 500 }
{ from: 500 }
]) {
name
count
}
}
items {
_score
Name
RelativePath
Description
DefaultMarketPrice
DefaultImageUrl
}
}
}React implementation with filters
Use this React component to build a product search interface with keyword input, price filters, and pagination. It dynamically fetches and displays products based on user input.
This component lets users search for products by keyword and price and displays search results, handles loading, and error states, and supports pagination:
import { gql, useQuery } from '@apollo/client';
import { useState } from 'react';
const PRODUCT_SEARCH = gql`
query ProductSearchWithInventory(
$searchTerm: String,
$minPrice: Float,
$maxPrice: Float,
$skip: Int = 0,
$limit: Int = 20
) {
GenericProduct(
where: {
_and: [
{
_or: [
{ _fulltext: { match: $searchTerm } }
{ Name: { match: $searchTerm, boost: 5 } }
{ Description: { match: $searchTerm, boost: 3 } }
]
}
{ DefaultMarketPrice: { gte: $minPrice, lte: $maxPrice } }
]
}
skip: $skip
limit: $limit
orderBy: { _ranking: SEMANTIC }
) {
total
facets {
DefaultMarketPrice(ranges: [
{ from: 0, to: 50 }
{ from: 50, to: 100 }
{ from: 100, to: 500 }
{ from: 500 }
]) {
name
count
}
}
items {
_score
Name
RelativePath
Description
DefaultMarketPrice
DefaultImageUrl
}
}
}
`;
export function ProductSearch() {
const [searchTerm, setSearchTerm] = useState('');
const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 });
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 20;
const { loading, error, data, refetch } = useQuery(PRODUCT_SEARCH, {
variables: {
searchTerm: searchTerm || undefined,
minPrice: priceRange.min,
maxPrice: priceRange.max,
skip: (currentPage - 1) * pageSize,
limit: pageSize
},
errorPolicy: 'partial'
});
const products = data?.GenericProduct?.items || [];
const priceFacets = data?.GenericProduct?.facets?.DefaultMarketPrice || [];
const totalResults = data?.GenericProduct?.total || 0;
const handleSearch = (e) => {
e.preventDefault();
setCurrentPage(1);
refetch();
};
return (
<div className="product-search">
<form onSubmit={handleSearch} className="search-form">
<input
type="text"
placeholder="Search products..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button type="submit">Search</button>
</form>
<p>{totalResults} products found</p>
<div className="filters">
<h3>Price Range</h3>
<input
type="number"
placeholder="Min"
value={priceRange.min}
onChange={(e) => setPriceRange(prev => ({ ...prev, min: Number(e.target.value) }))}
/>
<input
type="number"
placeholder="Max"
value={priceRange.max}
onChange={(e) => setPriceRange(prev => ({ ...prev, max: Number(e.target.value) }))}
/>
<div className="price-facets">
{priceFacets.map(facet => (
<div key={facet.name}>
{facet.name}: {facet.count} items
</div>
))}
</div>
</div>
<div className="products-grid">
{products.map(product => (
<div key={product.RelativePath} className="product-card">
{product.DefaultImageUrl && (
<img src={product.DefaultImageUrl} alt={product.Name} />
)}
<h3>
<a href={product.RelativePath}>{product.Name}</a>
</h3>
{product.Description && (
<p>{product.Description.substring(0, 100)}...</p>
)}
<p>${product.DefaultMarketPrice.toFixed(2)}</p>
<p>Relevance: {(product._score * 100).toFixed(1)}%</p>
</div>
))}
</div>
<div className="pagination">
<button disabled={currentPage === 1} onClick={() => setCurrentPage(prev => prev - 1)}>Previous</button>
<span>Page {currentPage} of {Math.ceil(totalResults / pageSize)}</span>
<button disabled={currentPage * pageSize >= totalResults} onClick={() => setCurrentPage(prev => prev + 1)}>Next</button>
</div>
</div>
);
}Updated 16 days ago
