A recap of a two-part stream held over on twitch.tv/jadenbaptista
Launching an ecommerce experience involves many different stakeholders. Developers, designers, product manager, content producers, marketers and merchandisers, each bring their own niche tools and workflows to the challenge to help them do their jobs better. For example, a marketer will usually prefer to use SendGrid over a custom internal emailing tool because it's more developed and easier to use. It's the same reason why product managers like using Github Issues, content producers like using a CMS, and merchandisers like using Shopify. It's up to developers to make all the different pieces fit into a single cohesive customer experience. This can be a real obstacle! Usually it requires backend development work and managing your own infrastructure, or at least it forces you to cobble some plugins together in WordPress or Shopify.
However, in the age of the Jamstack, there is a new way for front-end developers to solve this challenge. It's called an API Mesh.
Term definition: an API Mesh is kind of like a switchboard for APIs. You can set it up so that all the data and functionality you'd normally ping an API for (for example, from a CMS, or from Stripe, or from another other REST or GraphQL API) and pipe it into TakeShape (the original API Mesh). Then with one GraphQL request to TakeShape, you can access whatever data or functionality you ask it for, regardless of its original source.
The Plan
In a recent two-part stream over on my Twitch, I demonstrated the process of collecting data from multiple sources and using it to fill out a product page right on the client-side with TakeShape's API Mesh. The first part of the stream was mostly about planning, so here's what I came up with:
I decided that the majority of the data about the product itself should be stored in TakeShape's CMS (it's helpfully bundled in the same suite of tools as the API Mesh and a static site generator), while any data about pricing should be stored in Stripe. That last part might seem a bit strange to anyone who's used to working with traditional databases like MySQL: you'd think it would be better to have all the information about the product available from a single source, right?
Actually, it turns out that keeping all that product data in one place isn't necessary. Stripe makes for a good example here, since your pricing data is going to have to be stored there anyways if you're going to charge people using Stripe. If you have the price stored in your database (or CMS) along with all the other product data, then there will be some period of time — like when you're changing how much something costs — when it doesn't match exactly with the price you've got in Stripe. Then suddenly you've found yourself in a situation where you're advertising one price but charging the customer another.
To avoid scary situations like this, we need to keep Stripe as the single source of truth for any piece of data it reasonably should control. All of our prices and money-related data ought to be kept there and only there, while any other product attributes (like title, description, image, etc) could stay in the CMS.
The Steps
-
Fill our data sources with data.
This was actually the easy part, since I just borrowed one of TakeShape's awesome starter kits on Github (I used this one). It came with everything I needed to get started, so I didn't have to do much setup. I actually ended up deleting 6 of the 9 products that came with the starter project, just for simplicity. After creating a new Stripe account, I created a
product
and aprice
in Stripe for each of the remaining sellable items, and called it done. -
Tell the API Mesh about all of our sources.
The API Mesh needs to know where to collect data from if it's going to deliver it to us in one neat little packet, so we've got to give it our Stripe credentials and instructions on how to use their API. Now this might sound a bit complex, but it's really not; I went through this process live on the stream, and even with my impulsive rambling, it barely took 10 minutes.
All we really have to do is define these data sources as
service
s in TakeShape. They're listed under the Schema tab. Just click the "Connect Service" button to add a new one.To add a new service, click the Schema tab, and then the Connect Service button.
It'll walk you through a simple form just asking for the name of the service, the root endpoint (that's
[api.stripe.com](http://api.stripe.com)
in my case), and your auth details. Make sure to read the docs of the API you're using (here in my case) and the little note on the TakeShape form to make sure you're putting the right credentials in the right fields. In my initial stream, I made a tiny mistake here and used the wrong auth type. It wasn't a big deal; I just had to spend 5 minutes correcting it in the second stream. -
Tell the API Mesh to stitch data from those sources into one request.
For this, we're going to jump into a little bit of JSON. Click the Export button on the Schema page to download the JSON that represents your entire project.
Click the Export button on the Schema page to download JSON that represents your entire project.
The JSON it downloads might seem a bit unwieldy, but luckily most of it isn't relevant at the moment, so I use my code editor to fold a lot of it up so I can focus on what's important right now:
My folded-up, simple view of the JSON schema that represents my whole project
See the Stripe service we added earlier? The TakeShape GUI just edited this JSON file for us. For the queries, shapes, and forms, we're going to do essentially the same thing, except we're just going to edit the JSON file ourselves.
The first step for me was to go into the
Product
form and remove any property that mentions price data, since we're going to keep that in Stripe. If you're following along, make sure to check theorder
list too. Do the same thing in theProduct
shape, making sure to check therequired
list as well (that was another thing I briefly forgot about in the livestream, which I had to spend a minute or two correcting).Next, we've got to add a property to the
Product
shape calledproductId
, which would look something like this:
"productId": { "type": "string", "title": "Product ID", "minLength": 1 }
That goes in the
properties
array under theProduct
shape.The whole point of using this API Mesh in the first place was to help us developers stitch together all the tools our coworkers are using, so we've got to give whoever is in charge of maintaining the product entries the ability to enter the Stripe product ID into the CMS. TakeShape's CMS just displays
forms
to the user, which they can fill out to edit the product (or whatever other thing they're editing). So earlier, when we removed the price data from theProduct
form, we were removing the ability for our coworkers to add the price directly into TakeShape's CMS. Now that we want to let them add the Stripe product ID, we're going to add a new property to theProduct
form:
"productId": { "widget": "singleLineText" }
Now here comes the toughest part, though it's still fairly straightforward. We want TakeShape to automatically pull in price data from Stripe whenever we ask it about a product. In other words, we're asking TakeShape to resolve the product to its appropriate price data from Stripe. In line with that, let's give TakeShape a resolver so it knows what data to give us.
Go back to that
Product
shape and add this property:
"priceData": { "type": "object", "properties": { "inCents": { "type": "number" } }, "title": "Price Data", "@resolver": { "name": "rest:get", "service": "stripe", "options": { "fieldName": "priceData", "path": "v1/prices" }, "argsMapping": { "searchParams.product": [ [ "jsonPath", { "path": "$.source.productId" } ] ] }, "resultsMapping": { "inCents": [ [ "jsonPath", { "path": "steps[0].data[0].unit_amount" } ] ] } } }
Here's a little breakdown for the curious:
- First, we define that this is going to be an object, and that it'll have one property of its own called
inCents
, which will be a number. - Then, we tell it to get information from a GET request to the
stripe
service we defined earlier, at the/v1/prices
endpoint. - Then, the API mesh is instructed to send the
productID
of this particular product into Stripe as a search parameter. - Lastly, we're asking that the
unit_amount
buried in Stripe's response be piped into thatinCents
property we defined earlier, so that our end result ends up nice and tidy.
We're done with this file, so just reimport that JSON file back into TakeShape and let the API Mesh do its thing.
- First, we define that this is going to be an object, and that it'll have one property of its own called
-
Fill our product page with data from the API Mesh.
If we're going to hydrate our product page on the client, then we need to first request the data from our API Mesh. The GraphQL query we're sending to TakeShape would look something like this:
const query = `{ getProductList { items { description image { sourceUrl } name priceData { inCents } category { searchSummary } soldOut } } }`;
Here's the function I wrote to actually send that request:
const getMeMyData = async index => { let req = await fetch( `https://api.takeshape.io/project/${TS_PRODUCT_ID}/v3/graphql`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${TS_API_KEY}` }, body: JSON.stringify({ query }) } ); let result = await req.json(); return result.data.getProductList.items[index]; };
And then here's the function that uses that data to hydrate my HTML:
const hydrateMePage = async productIndex => { const product_data = await getMeMyData(productIndex); document.getElementById("product-title").innerText = product_data.name; document.getElementById("product-description").innerText = product_data.description; let productImage = document.getElementById("product-image"); productImage.src = product_data.image.sourceUrl; productImage.alt = product_data.name; document.querySelector(".product-price > span").innerText = "$" + (product_data.priceData.inCents / 100).toFixed(2); document.getElementById("product-category").innerText = product_data.category.searchSummary; if (product_data.soldOut) document.querySelector("main.container").classList.add("sold-out"); };
Those two small functions set us up to fill out our whole page with data from multiple sources without practically any technical debt. Just run a
hydrateMePage(0)
and it'll fill the page with data like magic; except that it's not magic, it's an API Mesh stitching together our CMS and Stripe data so we don't have to do the legwork of stitching it ourselves.
The Results
And with those four steps, we've suddenly created an entire data pipeline for our ecommerce website. To create a search results page, or a homepage, or anything else, we don't even have to replicate all the steps; depending on whether you need to add more sources or not, you'd just start from step 2 or 4.
If you're interested in seeing more builds like this, I stream over on my Twitch every Monday afternoon. We also talk with leaders in the web development community about their experiences with interactive interviews there on Thursday afternoons. Make sure to follow @takeshapeio and @jadenguitarman on Twitter to get the latest updates.