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:
We’ll write our server code in Express, so the first thing we need to do is set up Express and other necessary packages.
Install the following npm
packages from Wix:
npm install @wix/sdk npm install @wix/data
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.
Once your Express app is created, open the app.js
file and paste the following code:
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:
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.
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.
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:
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.
Paste the public key as the value for spki
.
Let’s start adding code to the try
block. We first add some error checking code that ensures the jwt
parameter is a string:
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():
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:
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.
return payload;
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.
catch (error) {
throw new Error('JWT verification failed');
}
The full code for the function should now look like this:
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");
}
}
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.
Go to the POST method we created below the verify()
function:
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
.
Inside the handler function add a try
-catch
block. In the try
block add the following lines of code:
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.
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:
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.
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.
Beneath the collection name, copy the collection ID.
Return to the Express code. Beneath the createClient() method we added, add this code:
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:
requestedItems
array.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.
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:
catch (error) {
response.status(400).send({ error: error.message });
}
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:
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.
The permission should now appear in the permissions list:
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
:
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.
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:
In the app dashboard, go to the extensions page.
Click + Create Extension and search for Ecom Catalog. Click + Create.
In the JSON editor, add a key deploymentUri
. As its value, paste the deployment URI of your server. For example:
{
"deploymentUri": "https://the-poems-manager.com"
}
Click Save. The Ecom Catalog extension now appears on our app’s extension list:
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.