Step 5 | Implement a Self-hosted Catalog Service Plugin

In Step 2, we created a catalog database to store the poems a Wix user sells on their site. In order to connect that catalog to the eCommerce platform, we need to create a server to implement the Catalog service plugin.

The service plugin includes a single method called Get Catalog Items. Wix calls this method when a cart or checkout on the Wix user’s site is updated. In the call, it includes the IDs of any items in the cart, and in return it expects information from the catalog about each item.

Wix doesn’t know where the catalog database is located–it only knows the location of the service plugin endpoint. Therefore, the service plugin must retrieve the items from the catalog database and return them to Wix in the correct format. In our case, this means our service plugin needs to communicate with the Wix site to access the catalog database and return the required item details.

In this example app, we’ve placed the catalog database on a Wix site. But remember that in your own business solution, you can choose to locate the catalog database anywhere, including on your server or in an external database. You just need to change your server logic to fetch from that location.

There are several things our server code must do to fulfill a Get Catalog Items request:

  1. Verify and decode the incoming request sent as a JSON web token.
  2. Extract the app instance ID and requested item IDs from the decoded payload.
  3. Create a client to communicate with the Wix site that sent the request.
  4. Use the client to request information from the site catalog database about each item.
  5. Return all item information in a form that matches the Get Catalog Items response object.

We’ll write our server code in Express, so the first thing we need to do is set up Express and other necessary packages.

  1. Install the following npm packages from Wix:

    Copy
    npm install @wix/sdk npm install @wix/data
  2. Install Express and set up an app. Use version 4.17.0. You can choose to use express-generator, but it isn’t necessary for this example.

  3. Once your Express app is created, open the app.js file and paste the following code:

    Copy
    const express = require("express"); const jose = require("jose"); const { createClient, AppStrategy } = require("@wix/sdk"); const { collections, items } = require("@wix/data"); const app = express(); const port = 3000; app.use(express.text()); async function verify(jwt) {} app.post("/get-catalog-items", async (request, response) => {}); app.listen(port, () => { console.log(`Example app listening on port ${port}`); }); module.exports = app;

    Most of the above code is standard Express. However, we’ve added some extra lines that are specific to our business solution:

    Lines 2-4. Import 3 libraries that we’ll need in our server code:

    • Jose for JWT verification.
    • Wix SDK to create a Wix client.
    • Wix Data to work with collection data on a site.

    Line 8. Set up the express.text() middleware function. This function parses the incoming request, including the encoded body, into a string that our verification function can handle.

    Line 10. Create our verify() function, which will accept a JWT token as a string and decode it. We’ll write the code for this in a moment.

    Line 12. A POST method that receives Get Catalog Items requests from Wix and returns the correct information from the catalog. We’ll write the code for this shortly.

Let’s start by writing the code for the verify() function.

Verify the JWT token

As with all service plugins at Wix, when a Wix site calls Get Catalog Items, it encodes the request body as a JWT. Therefore, in the service plugin we must decode the token in order to extract information like the app instance and catalog IDs. In this tutorial, we use the jose library for JWT verification.

  1. In the verify() function we created before, add a try-catch block. Above the try block, add 2 const variables alg and spki.

    The first variable defines the RSA algorithm we’re using. In the case of requests from Wix, this is RS256, so we’ll assign the variable this value as a string:

    Copy
    const alg = "RS256";

    The second variable, spki, is our public key. Each app in the Custom Apps list has a unique public key. To find this key, return to the App ID & keys modal in the app dashboard. Copy the public key from the modal.

    Copy the public key from the App ID & keys modal in your app dashboard

    Paste the public key as the value for spki.

  2. Let’s start adding code to the try block. We first add some error checking code that ensures the jwt parameter is a string:

    Copy
    try { if (typeof jwt !== "string") { throw new Error("JWT must be a string"); } } catch (error) {}

    Next, we add the functions that actually do the decoding. The first function we add is importSPKI():

    Copy
    const publicKey = await jose.importSPKI(spki, alg);

    We pass the spki and alg variables to importSPKI() and store the result in a new variable called publicKey. We then pass this result to the jwtVerify() function, along with the jwt parameter and an JWTVerifyOptions object:

    Copy
    const { payload, protectedHeader } = await jose.jwtVerify(jwt, publicKey, { issuer: "wix.com", audience: "<YOUR APP ID>", maxTokenAge: 60, clockTolerance: 60, });

    Set the attributes in the options parameter as shown. These options allow us to validate requests received from Wix. To set the audience value, copy your app ID from the app dashboard.

    Finally, we return the payload.

    Copy
    return payload;
  3. Now we’ll add code to the catch block. You can add more complex error handling later, but for now we’ll simply throw an error with a message that the verification failed.

    Copy
    catch (error) {     throw new Error('JWT verification failed');   }

The full code for the function should now look like this:

Copy
async function verify(jwt) { const alg = "RS256"; const spki = `<YOUR PUBLIC KEY>`; try { if (typeof jwt !== "string") { throw new Error("JWT must be a string"); } const publicKey = await jose.importSPKI(spki, alg); const { payload, protectedHeader } = await jose.jwtVerify(jwt, publicKey, { issuer: "wix.com", audience: "<YOUR APP ID>", maxTokenAge: 60, clockTolerance: 60, }); return payload; } catch (error) { throw new Error("JWT verification failed"); } }

Implement Get Catalog Items

Now that we’ve set up our verify() function, we can implement the Get Catalog Items endpoint, so that Wix can make calls to our server to get information about catalog products.

  1. Go to the POST method we created below the verify() function:

    Copy
    app.post("/get-catalog-items", async (reqest, response) => {});

    Note a couple of important things here:

    • The path we route catalog requests to must be in snakecase. Make sure your  path is identical to the one shown above.

    • We will need to make asynchronous calls inside our handler function, so mark it as async.

  2. Inside the handler function add a try-catch block. In the try block add the following lines of code:

    Copy
    const token = request.body; const body = await verify(token); const instanceId = body.data.metadata.instanceId; const requestedItems = body.data.request.catalogReferences;

    Let’s break down what this code is doing:

    Line 1. Isolate the request body, which is in JWT format, and store it in a variable token.

    Line 2. Pass the token to verify(). If the token is valid, verify() returns the request body as a decoded object, which we store in a JS object called body.

    Lines 3-4. Parse body to extract 2 important pieces of information:

    • The instance ID. This tells us which instance of our app made the request. We’ll use this shortly to query the site catalog.

    • The list of items that the Wix site is requesting information about. This tells us which items to retrieve information about from the catalog.

  3. Because our server is self-hosted, we need to create a client in order to make calls to a Wix site. We use the Wix SDK createClient() function to do this.

    createClient() expects a configuration object with at least 2 attributes:

    • modules: The Wix SDK modules your self-hosted app uses. This typically matches the modules that you imported.

    • Your app’s authentication strategy. In our example the app will make API calls, so we use the AppStrategy authentication strategy.

    Add the following code to the try block:

    Copy
    const wixClient = createClient({ modules: { items }, auth: AppStrategy({ appId: "<YOUR APP ID>", appSecret: "<YOUR APP SECRET KEY>", publicKey: `<YOUR PUBLIC KEY>` instanceId: instanceId, }), });

    Line 2. Define the modules we use in this client. In this case, we only use the items module.

    Lines 3-8. Define the AppStrategy object. This object requires your app ID, app secret, and public key from the app dashboard.

    The AppStrategy object also requires the ID of the app instance. This tells the app which site it’s communicating with. We take the ID we parsed from the decoded request body and pass it to the AppStrategy constructor.

    This sets up our client and prepares us to make calls to the site collection.

  4. Before adding the next piece of code, we need the ID of the Poems collection we created in Step 2. Return to the Blocks app and go to your collection in the CMS. Click Edit Settings to open the collection settings.

    Go to CMS settings in Blocks to get your collection ID

    Beneath the collection name, copy the collection ID.

  5. Return to the Express code. Beneath the createClient() method we added, add this code:

    Copy
    const catalogItems = await Promise.all( requestedItems.map(async (reference) => { const results = await wixClient.items .query("<COLLECTION ID>") .eq("mainProductId", reference.catalogReference.catalogItemId) .find(); const item = results.items[0]; const options = reference.catalogReference.options !== null ? reference.catalogReference.options : {}; return { catalogReference: { appId: "<YOUR APP ID>", catalogItemId: item.mainProductId, options: options, }, data: { productName: { original: item.title, }, itemType: { preset: "SERVICE", }, price: item.price, priceDescription: { original: "Number of lines", }, }, }; }), ); response.send({ catalogItems });

    Before we review this code line by line, it’s important to understand its overall purpose. The code takes in the requestedItems array we created containing catalog references for the requested items, and returns a new array of catalog items in the required format. To do so, it performs the following steps:

    1. Extracts the catalog ID of each item in the requestedItems array.
    2. Queries the site catalog for that ID.
    3. Organizes the returned information from the site into an object that matches the Get Catalog Items response.

    Let’s break down the code line by line:

    Line 1. Wrap the Javascript map() function in a Promise.all() statement that returns the array of objects only after the query has been resolved for all items.

    Lines 3-6. Using the Wix client we created, query the catalog database on the site. Paste your collection ID as the parameter.

    Line 7. Store the query result.

    Line 8. Extract the variant information from the requested item and place it in an object options that can be passed to the returned item.

    Lines 10-28. Set up the response object in the correct format.

    Line 31. Returns the complete array of catalog references catalogItems to the caller.

  6. To complete the Get Catalog Items endpoint, we’ll fill out the catch block. As with verify(), you can add more complex error handling later, but for now we’ll just return a 400 status with an error message:

    Copy
    catch (error) {     response.status(400).send({ error: error.message });   }
  7. Our server code is complete, but we need to complete one extra step to make sure it can communicate with the product catalog on the site.

    In our code, we call the query() method of the Wix Data items module. Like all API methods at Wix, in order for our app to call this method, it must have the correct permissions. We can locate these permissions in the reference above the method declaration:

    Location of API method permissions

    The specific permission we need to call query() is READ DATA ITEMS. To add this permission to our app, return to the app dashboard and go to permissions. Click + Add Permissions.

    The easiest way to locate the permission you need to add is to search by name or ID. Search for “read data items”. Select the correct permission and click Save to add it to your app.

    Add read data items permissions to your app

    The permission should now appear in the permissions list:

    Permissions list app dashboard

    Now our app can communicate with the product catalog on a site.

Our service plugin code is now complete. Here’s the full code for app.js:

Copy
const express = require("express"); const jose = require("jose"); const { createClient, AppStrategy } = require("@wix/sdk"); const { collections, items } = require("@wix/data"); const app = express(); const port = 3000; app.use(express.json()); app.use(express.text()); async function verify(jwt) { const alg = "RS256"; const spki = `<YOUR PUBLIC KEY>`; try { if (typeof jwt !== "string") { throw new Error("JWT must be a string"); } const publicKey = await jose.importSPKI(spki, alg); const { payload, protectedHeader } = await jose.jwtVerify(jwt, publicKey, { issuer: "wix.com", audience: "<YOUR APP ID>", maxTokenAge: 60, clockTolerance: 60, }); return payload; } catch (error) { throw new Error("JWT verification failed"); } } app.post("/get-catalog-items", async (request, response) => { try { const token = request.body; const body = await verify(token); const instanceId = body.data.metadata.instanceId; const requestedItems = body.data.request.catalogReferences; const wixClient = createClient({ modules: { collections, items }, auth: AppStrategy({ appId: "<YOUR APP ID>", appSecret: "<YOUR APP SECRET KEY>", publicKey: `<YOUR PUBLIC KEY>`, instanceId: instanceId, }), }); const catalogItems = await Promise.all( requestedItems.map(async (reference) => { const results = await wixClient.items .query("<COLLECTION ID>") .eq("mainProductId", reference.catalogReference.catalogItemId) .find(); const item = results.items[0]; const options = reference.catalogReference.options !== null ? reference.catalogReference.options : {}; return { catalogReference: { appId: "<YOUR APP ID>", catalogItemId: item.mainProductId, options: options, }, data: { productName: { original: item.title, }, itemType: { preset: "SERVICE", }, price: item.price, priceDescription: { original: "Number of lines", }, }, }; }), ); response.send({ catalogItems }); } catch (error) { response.status(400).send({ error: error.message }); } }); app.listen(port, () => { console.log(`Example app listening on port ${port}`); }); module.exports = app;

Deploy your code on your chosen server.

Create an Ecom Catalog in the App Dashboard

Once we deploy our server code, we need to tell Wix where to send its requests. To do this, we’ll return to our app dashboard and create an Ecom Catalog extension:

  1. In the app dashboard, go to the extensions page.

  2. Click + Create Extension and search for Ecom Catalog. Click + Create.

  3. In the JSON editor, add a key deploymentUri. As its value, paste the deployment URI of your server. For example:

    Copy
    {   "deploymentUri": "https://the-poems-manager.com" }
  4. Click Save. The Ecom Catalog extension now appears on our app’s extension list:

    Extensions list app dashboard

Our app is now complete and able to function on a site. In the last article, we'll build the app, install it on a site, and test the functionality we created throughout this tutorial.

Next up: Step 6 | Test your Business Solution on a Site

Did this help?