Using API Mesh to Streamline Ecommerce Development - A Stream Summary

Jaden Baptista - May 24 '21 - - Dev Community

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

  1. 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 a price in Stripe for each of the remaining sellable items, and called it done.

  2. 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 services in TakeShape. They're listed under the Schema tab. Just click the "Connect Service" button to add a new one.

    https://images.takeshape.io/4d46e476-8704-42c4-8d0d-06ebdd0e3c93/dev/04691591-aa81-4b0b-bc43-31b4ed14ad10/screenshot1.png?auto=compress%2Cformat

    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.

  3. 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.

    https://images.takeshape.io/4d46e476-8704-42c4-8d0d-06ebdd0e3c93/dev/b646c274-af81-429f-bcf6-76a19beec944/export-button.png?auto=compress%2Cformat

    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:

    https://images.takeshape.io/4d46e476-8704-42c4-8d0d-06ebdd0e3c93/dev/884e93b6-3a5d-496d-a2cd-21bfe64115c0/schema.png?auto=compress%2Cformat

    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 the order list too. Do the same thing in the Product shape, making sure to check the required 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 called productId, which would look something like this:

    "productId": {
        "type": "string",
        "title": "Product ID",
        "minLength": 1
    }
    

    That goes in the properties array under the Product 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 the Product 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 the Product 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:

    1. 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.
    2. Then, we tell it to get information from a GET request to the stripe service we defined earlier, at the /v1/prices endpoint.
    3. Then, the API mesh is instructed to send the productID of this particular product into Stripe as a search parameter.
    4. Lastly, we're asking that the unit_amount buried in Stripe's response be piped into that inCents 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.

  4. 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .