In this post we want to explore the use of a Gatsby powered front-end with HCL Commerce as a headless commerce platform. The main goal is to replace the Aurora starter store basic functions with a Gatsby powered store. The store should have a home page (HP), product list page (PLP), product details page (PDP) and checkout. The intention of the post is to give the general idea of how this can be accomplished along with a few code examples.

Setting up

To set up your development environment we need to take the following steps
  • Install Node.js: Download and install the latest Node.js version from the official Node.js website
  • Install Git
  • Install Gatsby CLI: The Gatsby CLI is available via npm and should be installed globally by running npm install -g gatsby-cli
  • Create a Gatsby site: gatsby new gatsby https://github.com/gatsbyjs/gatsby-starter-hello-world

Now we can do cd gatsby and then gatsby develop to start the Gatsby development server. We can access the Gatsby starter site at http://localhost:8000/. For more info and tutorials on Gatsby, see the excellent Gatsby docs.

Sourcing data and generating pages

Gatsby can pull data from headless CMSs, databases, APIs and among others. There are three approaches that you can use to source data from your private API:
  1. If your private API is a GraphQL API, you can use gatsby-source-graphql.
  2. If your private API is not a GraphQL API and you are new to GraphQL, treat the data as unstructured data and fetch it during build time, as described by the guide ”Using Gatsby without GraphQL”. However, as highlighted in the guide, this approach comes with some tradeoffs.
  3. Create a source plugin, as described in the tutorial ”Source plugin tutorial

We will describe how we can use option two to source the data we need from the HCL Commerce APIs and create pages for the store. The gatsby-node.js file is used to source the data and create the PDPs and PLPs:

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.org/docs/node-apis/
 */

// You can delete this file if you're not using it
const axios = require('axios');

const get = endpoint => axios.get(`http://localhost:80/wcs/resources/store/1/productview/byCategory/10001`);

const getProductData = categoryIds =>
Promise.all(
    categoryIds.map(async id => {
        const { data: CatalogEntryView } = await get(``);
        return { ...CatalogEntryView };
    })
);

exports.createPages = async ({ actions: { createPage } }) => {
    // `getProductData` is a function that fetches our data
    const allCategory = await getProductData(["10001"]);
  
    // Create a page that lists all Product.
    allCategory.forEach(category => {
        createPage({
        path: `/category/10001`,
        component: require.resolve("./src/templates/category.js"),
        context: { category },
        })
    })
  
    // Create a page for each product.
    allCategory.forEach(category => {
        category.CatalogEntryView.forEach(product => {
            createPage({
                path: `/category/10001/product/${product.uniqueID}/`,
                component: require.resolve("./src/templates/product.js"),
                context: { product },
            })
        })
    })
  }
The following templates for the PLP and PDP respectively are found in src\templates\
import React from 'react';
import { Link } from 'gatsby';

export default ({ pageContext: { category } }) => (
  <div style={{ width: 960, margin: '4rem auto' }}>
    <h1>Product List Page - PLP</h1>
    <h1>Total Products : {category.recordSetCount}</h1>
    <ul style={{ padding: 0 }}>
      {category.CatalogEntryView.map(product => (
        <li
          key={product.uniqueID}
          style={{
            textAlign: 'center',
            listStyle: 'none',
            display: 'inline-block'
          }}
        >
          <Link to={`/category/10001/product/${product.uniqueID}`}>
            <img src={`https://localhost:8443${product.thumbnail}`} alt={product.partNumber} />
            <p>{product.partNumber}</p>
          </Link>
        </li>
      ))}
    </ul>
  </div>
);
import React from 'react';
import { Link } from 'gatsby';

export default ({ pageContext: { product } }) => (
    <div style={{ width: 960, margin: "4rem auto" }}>
      <h1>{product.uniqueID}</h1>
      <h1>{product.name}</h1>
        <h5>{product.shortDescription}</h5>
        <h3>${product.Price[0].priceValue}</h3>
      <h1>{product.partNumber}</h1>
      <img src={`https://localhost:8443${product.thumbnail}`} alt={product.name} />
    </div>
  )

Now, after we build the code we can see all pages the are created under public\page-data

  • public\page-data\category\page-data.json
  • public\page-data\category\10001\page-data.json
  • public\page-data\category\10001\product\10039\page-data.json

We could also have sourced the data by creating a source plug-in and then used graphQL to reference it in gatsby-node.js. More info on source plug-ins in Gatsby here, but at a high-level a plug-in would do the following:

  • Accept config options like an API key and/or a search query
  • Make an API request (to the HCL Commerce REST API) using the provided config options
  • Convert the data in the API response to Gatsby’s node system

Shopping cart

The PDPs and PLPs were generated pages that used data sources and template files to prepare the final HTML. For the shopping cart we will just prepare a single page, and we do that by adding a file  src/pages/cart.js, so no templates are used this time around. We will call the HCL Commerce endpoint that provides details on the shopping cart: http://localhost:80/wcs/resources/store/1/cart/@self?pageSize=50&pageNumber=1&responseFormat=json'. This will provide us with a JSON that contains everything that is in the cart. We loop through the response to create the shopping cart:

import React, { useState, useEffect } from "react"
import { Link } from "gatsby"

import Layout from "../components/layout"
import SEO from "../components/seo"

const axios = require('axios');
const fetch = require(`node-fetch`);

const CartPage = () => {
  const cartURL = 'http://localhost:80/wcs/resources/store/1/cart/@self?pageSize=50&pageNumber=1&responseFormat=json';

  // Client-side Runtime Data Fetching
  const [cartData, setCartData] = useState(0)
  useEffect(() => {
    // get data from GitHub api
    fetch(cartURL, {credentials: "include"})
      .then(response => response.json()) // parse JSON from request
      .then(resultData => {
        setCartData(resultData)
      })
  }, [])
  return (
    <Layout>
      <SEO title="Shoping Cart" />
      <h1>Shopping cart</h1>
      <table>
        <tr>
          <th>Product</th>
          <th>QTY</th>
          <th>Each</th>
          <th>Total</th>
        </tr>
        {cartData.orderItem && cartData.orderItem.map(item => (
          <tr>
            <td><Link to={`/category/10001/product/${item.productId}`}>{item.partNumber}</Link></td>
            <td>{item.quantity}</td>
            <td>{item.unitPrice}</td>
            <td>{item.orderItemPrice}</td>
          </tr>
        ))}
        <tr>
          <td></td>
          <td></td>
          <td>Order Subtotal:</td>
          <td>{cartData.totalProductPrice}</td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td>Discount:</td>
          <td>({cartData.totalAdjustment})</td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td>Order Total:</td>
          <td>{cartData.grandTotal}</td>
        </tr>
      </table>
    </Layout>
  )
}

export default CartPage;

Gatsby Shopping Cart

It is not enough to have a shopping cart, we need a way to add items to the shopping cart. In order to do that we need add some functionality to the PDPs. To get product details we can use GET /wcs/resources/store/10001/productview/byId/13011 endpoint. Then to add to the cart we use something like

POST /wcs/resources/store/10001/cart?responseFormat=json
{
	"orderId": ".",
	"orderItem": [
		{
			"productId": "10706",
			"quantity": "1"
		}
	],
	"x_calculateOrder": "0",
	"x_inventoryValidation": "true"
}
After adding this additional logic to the PDP, we will end up with something like the following:

Gatsby PDP

Checkout

We will need to build a checkout flow for the store as well. That will involve three views: Shipping/Billing page, Payment/Review page and a Confirmation page. We will do that similar to the Shopping Cart page; that is, we will add a new entry to src/pages for each page. For each page we will also have to call to proper HCL Commerce endpoints to send and receive data. The resulting pages look like this:

Shipping and Billing information

Payment and Review

Confirmation page

Espots and CMC content

If we need to display Espots or other content from the CMC, it is easily accessible via the HCL Commerce REST API. The following table summarizes some of the available endpoints

HTTP Method

Path

Description

GET

/store/{storeId}/espot?q={q}

Retrieve a list of all search terms that have search rules.

GET

/store/{storeId}/espot/{name}

Gets an e-Marketing Spot by name.

GET

/store/{storeId}/espot/{name}/category/{categoryId}

Deprecated: Gets an e-Marketing Spot at a specific category level. For example, Furniture. Alternatively, this can be implemented using the findByName method with the following URL: store/{storeId}/espot/{name}?categoryId=&DM_ReqCmd=CategoryDisplay.

GET

/store/{storeId}/espot/{name}/product/{productId}

Deprecated: Gets an e-Marketing Spot for a specific product. Alternatively, this can be implemented using the findByName method with the following URL: store/{storeId}/espot/{name}?productId=&DM_ReqCmd=ProductDisplay.

GET

/store/{storeId}/espot/{name}/type/{type}

Gets e-Marketing Spot data.

Conclusion

We have built a functional front-end for HCL commerce using the Gatsby framework. The store has all the basic functionalities needed to perform a checkout, such as add-to-cart and a checkout flow. The user will be able to browser the entire catalog and select different versions of the products to add to their cart.