loading...
Commerce.js

Part 1 - Algolia powered product search with Commerce.js

notrab profile image Jamie Barton Updated on ・7 min read

In this series we will create an Algolia powered faceted product search for Chec. Chec (or also known as Commerce.js) is a eCommerce API that gives you ultimate flexibility when building a frontend for your store.

Throughout this tutorial series we will be using:

Each time a product is created, updated or deleted, we will update Algolia to reflect those changes and searching products super easy for our users!

Because Chec has a hosted storefront (or "space"), we'll link all our products to the storefront page where users can proceed to pay for them!

1. Signup to Algolia

In this first step, create an account with Algolia, or login if you have an account already.

Next create a new application in Algolia. If you're signing up for the first time, this will be part of the onboarding process.

Once we've an application, Algolia will ask you to create an index. Make sure to call this products, as we'll need this later.

Finally, you'll need your Application ID, Search-Only API Key and Admin API Key

Algolia API Keys

2. Setup Next.js

We'll store all our server side and client side code inside our Next.js application. If you're experienced with Next, and have a project already, you can probably skip to installing the algolia dependencies below.

To create a new Next.js project, you can quickly get started using the CLI:

npm init next-app
# or
yarn create next-app

Once your project folder has been created, change directory and open your code editor.

cd your-project-name

3. Configure our webhook endpoint

We'll be configuring Chec next to send our product payload to our Next.js application so we can send this onto Algolia.

Start by installing the algoliasearch NPM dependency:

npm install algoliasearch
# or
yarn add algoliasearch

Now let's create the file algolia.js inside a new folder called api inside the existing pages folder.

mkdir pages/api
touch pages/api/algolia.js

Inside pages/api/algolia.js let's first initialize the algoliasearch dependency and assign it to client.

You'll need your Application ID and Admin API Key to do this!

import algoliasearch from "algoliasearch"

const client = algoliasearch("APPLICATION_ID", "ADMIN_API_KEY")

To confirm this is configured and working, let's initialize the products index and add a new object to the index.

Below our client definition, add:

const algoliaHandler = async (req, res) => {
  const index = client.initIndex('products')

  const product = await index.saveObject({
    objectID: 'myId',
    name: 'Sneakers',
    price: 1000
  })

  res.send(product)
}

export default algoliaHandler

Now let's try it out! Now start Next.js...

npm run dev
# or
yarn dev

... and go to http://localhost:3000/api/algolia. You should see your objectID returned along with a taskID.

If you open the application on Algolia, and browse the products Indicies, you should see the record here also!

Algolia Indicies Page

Great! 🎉 We now have a successful serverless function that is connecting to Algolia.

4. Configure Chec webhooks

In order to properly sync data to Algolia from Chec, we need to configure our webhook and listen for the following events:

  • products.create
  • products.update
  • products.delete

Now head to the Chec Merchant Dashboard and go to Setup > Webhooks, and click + Add Webhook.

First select the Events we need to watch for.

Since we can't forward requests from the outside world to localhost:3000, we'll use a CLI tool called ngrok to get a URL that will handle tunnelling requests for us.

Once you've installed ngrok, inside a new terminal run:

ngrok http 3000

Remember: We have Next.js running on port 3000, so that's why we're forwarding requests there.

When ngrok is running, you'll get a Forwarding address.

Let's use the http forwarding address and add append /api/algolia to the URL, and Add webhook!

Chec Add Webhook Modal


Next we'll need to update our /api/algolia.js function to correctly handle the payload from Algolia and pass it on!

5. Inspecting the Chec payload

Once you create a new product via the Chec API, the URL we provided to ngrok (localhost:3000) will be invoked.

The payload will tell us if a product was created, updated or deleted, and we can use this perform the appropriate action with our Algolia client.

If a product is created or updated, we'll want to use index.saveObject() but if a product is deleted, we'll need to use index.deleteObject().


 Example payload

{
  "id": "wbhk_QO3bR5XAlnzdjX",
  "created": 1587214321,
  "event": "products.create",
  "response_code": 201,
  "payload": {
    "id": "prod_4OANwRKEbovYL8",
    "created": 1587897613,
    "last_updated": 1587897613,
    "active": true,
    "permalink": "g9eASZ",
    "name": "My other test product",
    "description": null,
    "price": {
      "raw": 10,
      "formatted": "10.00",
      "formatted_with_symbol": "£10.00",
      "formatted_with_code": "10.00 GBP"
    },
    "quantity": 0,
    "media": [],
    "sku": null,
    "meta": [],
    "conditionals": {
      "is_active": true,
      "is_free": false,
      "is_tax_exempt": false,
      "is_pay_what_you_want": true,
      "is_quantity_limited": false,
      "is_sold_out": false,
      "has_digital_delivery": false,
      "has_physical_delivery": false,
      "has_images": false,
      "has_video": false,
      "has_rich_embed": false,
      "collects_fullname": false,
      "collects_shipping_address": false,
      "collects_billing_address": false,
      "collects_extrafields": false
    },
    "is": {
      "active": true,
      "free": false,
      "tax_exempt": false,
      "pay_what_you_want": true,
      "quantity_limited": false,
      "sold_out": false
    },
    "has": {
      "digital_delivery": false,
      "physical_delivery": false,
      "images": false,
      "video": false,
      "rich_embed": false
    },
    "collects": {
      "fullname": false,
      "shipping_address": false,
      "billing_address": false,
      "extrafields": false
    },
    "checkout_url": {
      "checkout": "https://checkout.chec.io/g99ASZ?checkout=true",
      "display": "https://checkout.chec.io/g99ASZ"
    },
    "categories": [],
    "urls": {
      "product": "/v1/products/prod_4OANwRKEbovYL8"
    }
  },
  "signature": "60eecbb88efa2c712204e3b0214dffb0317e297cbc01a6b592ca4287c4177cb9"
}

6. Syncing with Algolia

From the example payload above, we'll toggle our behaviour on the event value, and using JavaScript, we can split on . and get the action create, update or delete.

The product object is also inside the payload object which we can use to identify the correct record in our index.

Now, within the algoliaHandler function inside pages/api/algolia.js and after we initialize our Algolia client, replace the example code we wrote before with the following:

const { event, payload } = req.body
const [ _, eventType ] = event.split('.')
const { id: objectID, ...product } = payload

Here we're destructuring id from payload, and renaming it to objectID, then spreading the rest of the payload into product.

Algolia requires each record in the index to have a objectID value, so it makes sense to use our product ID for this.

Deleting products from Algolia

Let's first handle deleting our product from the index if the event type is product.deleted.

Let's add the following inside our function below the above code:

if (eventType === 'delete') {
  await index.deleteObject(objectID)
  return res.status(202).end()
}

Adding/Updating products to Algolia

If eventType is not delete, then we'll next have to handle calling the saveObject function.

We'll check if the eventType is next either create or update and call the saveObject method.

if (['create', 'update'].includes(eventType)) {
  await index.saveObject({ objectID, ...product })
  return res.status(200).send({ success: true })
}

Next, eventType is not create, update or delete, then let's return a message. You could do some special logging here to get notified when an index fails, but I'll leave that up to you!

res.send({
  message: `Event type ${eventType} is not a valid trigger`
})

Our pages/api/algolia.js function should look like...

import algoliasearch from "algoliasearch"

const client = algoliasearch("APPLICATION_ID", "ADMIN_API_KEY")

const algoliaHandler = async (req, res) => {
  const index = client.initIndex("products")

  const { event, payload } = req.body
  const [_, eventType] = event.split(".")
  const { id: objectID, ...product } = payload

  if (eventType === "delete") {
    await index.deleteObject(objectID)
    return res.status(202).end()
  }

  if (["create", "update"].includes(eventType)) {
    await index.saveObject({ objectID, ...product })
    return res.status(200).send({ success: true })
  }

  res.send({
    message: `Event type ${eventType} is not a valid trigger`,
  })
}

export default algoliaHandler

Let's test the above works!

7. Triggering our webhook!

Now let's use cURL to create, update and delete Chec products. You could also run the same command with Postman, Insomnia, etc.

Add a new product

🚨 Make sure to replace SECRET_KEY_HERE with your Chec Secret Key!

curl --location --request POST 'https://api.chec.io/v1/products' \
--header 'X-Authorization: SECRET_KEY_HERE' \
--header 'Content-Type: application/json' \
--data-raw '{
    "product": {
        "name": "My test product",
        "price": 1000
    }
}'

The response will look a little something like:

{"id":"prod_4WJvlK6Rx5bYV1","created":1587897846,"last_updated":1587897846,"active":true,"permalink":"b5BKLR","name":"My test product","description":null,"price":{"raw":1000,"formatted":"1,000.00","formatted_with_symbol":"\u00a31,000.00","formatted_with_code":"1,000.00 GBP"},"quantity":0,"media":[],"sku":null,"meta":[],"conditionals":{"is_active":true,"is_free":false,"is_tax_exempt":false,"is_pay_what_you_want":false,"is_quantity_limited":false,"is_sold_out":false,"has_digital_delivery":false,"has_physical_delivery":false,"has_images":false,"has_video":false,"has_rich_embed":false,"collects_fullname":false,"collects_shipping_address":false,"collects_billing_address":false,"collects_extrafields":false},"is":{"active":true,"free":false,"tax_exempt":false,"pay_what_you_want":false,"quantity_limited":false,"sold_out":false},"has":{"digital_delivery":false,"physical_delivery":false,"images":false,"video":false,"rich_embed":false},"collects":{"fullname":false,"shipping_address":false,"billing_address":false,"extrafields":false},"checkout_url":{"checkout":"https:\/\/checkout.chec.io\/b5BKLR?checkout=true","display":"https:\/\/checkout.chec.io\/b5BKLR"},"categories":[],"urls":{"product":"\/v1\/products\/prod_4WJvlK6Rx5bYV1"}}

If you open the Algolia Indices, you should see a new record for our new Chec product!

Update an existing product

🚨 Make sure to replace SECRET_KEY_HERE with your Chec Secret Key AND replace prod_4WJvlK6Rx5bYV1 with your product ID!

Using the id from the response above, let's now update the product price!

curl --location --request PUT 'https://api.chec.io/v1/products/prod_4WJvlK6Rx5bYV1' \
--header 'X-Authorization: SECRET_KEY_HERE' \
--header 'Content-Type: application/json' \
--data-raw '{
    "product": {
        "name": "My test product",
        "price": 2000
    }
}'

If you open the Algolia Indices, you should see the updated product record with our new price!

Delete an existing product

🚨 Make sure to replace SECRET_KEY_HERE with your Chec Secret Key AND replace prod_4WJvlK6Rx5bYV1 with your product ID!

curl --location --request DELETE 'https://api.chec.io/v1/products/prod_4WJvlK6Rx5bYV1' \
--header 'X-Authorization: SECRET_KEY_HERE'

The response will look like:

{
  "id": "prod_4WJvlK6Rx5bYV1",
  "deleted": true
}

If you open the Algolia Indices, you will now notice the record has been removed!

What's next?

Now we've a successful function that is triggered every time our Chec product catalog is modified, we can move onto creating a UI using Algolia InstantSearch to filter our products!

Posted on Apr 27 by:

notrab profile

Jamie Barton

@notrab

Husband. Dad. Full Stack Developer.

Commerce.js

API-first eCommerce platform

Discussion

markdown guide