Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 36

Client-Side Product Fetching with Storefront API

Recommended resources

Hello! Welcome back to the course.

In our last lesson, we explored the "why" of headless commerce, establishing the purpose of the Storefront API as the key that unlocks building custom front-end experiences decoupled from Shopify's theme engine. You learned how it enables developers to use any tech stack—like the React and Next.js you're proficient in—to create unique, high-performance storefronts.

Today, we transition from the "why" to the "how." The learning outcome for this lesson is to fetch product data using the Storefront API from a client-side JavaScript application. We'll get our hands dirty by making a live API call to your development store. We'll start with the fundamentals using the browser's native fetch API, which will give you a first-principles understanding of the mechanics before you ever layer on frameworks or libraries.

1. Getting Access: Your Storefront API Token

Before we can ask Shopify for data, we need to authenticate our request. The Storefront API uses a public access token. Unlike the private Admin API keys (which must be kept secret on a server), this token is safe to include in your client-side JavaScript code. It's "public" because it only grants access to data that a customer is meant to see, like published products and prices.

To get your token, you'll need to create a custom app in your Shopify Partner dashboard and configure its permissions.

The easy way to do the Shopify Storefront API

This blog post provides a clear, illustrated guide on how to create a custom app and retrieve your Storefront Access Token. This is the essential first step.

Follow the instructions in 'Step 1 - Create a Private App'. Your goal is to get to the 'API credentials' tab for your new app and copy the 'Storefront API access token'. When configuring the 'Storefront API scopes', you can start by granting read access to 'Products' and 'Product listings'.

Once you have your Storefront Access Token, keep it somewhere safe. You'll need it in a moment. You will also need your store's GraphQL API endpoint URL, which follows this format:
https://{your-store-name}.myshopify.com/api/2024-04/graphql.json

Remember to replace {your-store-name} with the name of your development store (e.g., quickstart-123ab45c).


2. Composing the Request: Writing a GraphQL Query

The Storefront API uses GraphQL, not REST. A key difference you'll appreciate is that with GraphQL, you specify exactly which data fields you want in your request, and the server returns only that data. This prevents the "over-fetching" common with REST APIs, where you get a huge object with data you don't need.

The best way to learn and build your queries is by using an interactive tool. Shopify provides the GraphiQL app for this purpose.

Building a Headless Ecommerce Store with Tailwind CSS, Shopify, and Next.js

This video provides a great demonstration of how to use an in-admin GraphQL explorer to build and test queries for fetching product data. It also explains the common edges and node structure you'll encounter.

Watch from 05:30 to 07:52. Focus on how the query is constructed piece by piece. Pay attention to the nested structure for products (products, edges, node) and for images. Don't worry about the Next.js context for now; just focus on the query itself.

Now, let's build a query. In your own GraphiQL app (or another API client like Postman), try writing a query to fetch the first five products. You can specify the fields you want for each product inside the node block.

Here is a clear example of a GraphQL query and the structured JSON response you can expect.

Shopify Storefront API GraphQL Query and Response for Product Data
This image shows a GraphQL query on the left requesting specific fields for products and their variants. On the right is the corresponding JSON response, structured exactly as requested.

A basic query to get started looks like this:

query {
  products(first: 5) {
    edges {
      node {
        id
        title
        handle
        descriptionHtml
      }
    }
  }
}

The edges and node syntax is part of the GraphQL Cursor Connections Specification. It's a standard way to structure lists of data that facilitates pagination, which we'll touch on in a later module. For now, just know that your list of products will be inside the data.products.edges array, and each item in the array is an object with a node property containing the product data.


3. Making the Call: Fetching Data with Client-Side JavaScript

Now we have all the pieces: the API endpoint, the access token, and a query. Let's put them together in a client-side JavaScript application. For simplicity, we'll use a single HTML file.

Create a file named index.html on your local machine and paste the following code into it. Remember to replace the placeholder values with your actual Storefront Access Token and GraphQL URL.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Shopify Storefront API Test</title>
</head>
<body>
    <h1>Fetching Products from Shopify...</h1>
    <div id="products"></div>

    <script>
        // 1. Your Storefront API credentials
        const storefrontAccessToken = 'YOUR_STOREFRONT_ACCESS_TOKEN'; // Replace with your token
        const graphqlUrl = 'https://YOUR_STORE.myshopify.com/api/2024-04/graphql.json'; // Replace with your URL

        // 2. The GraphQL query
        const productQuery = `
            query {
              products(first: 5) {
                edges {
                  node {
                    id
                    title
                    handle
                    descriptionHtml
                    priceRange {
                        minVariantPrice {
                            amount
                            currencyCode
                        }
                    }
                  }
                }
              }
            }
        `;

        // 3. The `fetch` request options
        const options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Shopify-Storefront-Access-Token': storefrontAccessToken,
            },
            body: JSON.stringify({ query: productQuery }),
        };

        // 4. Making the API call
        fetch(graphqlUrl, options)
            .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                return response.json();
            })
            .then(data => {
                console.log('API Response:', data); // Log the full response to inspect
                const products = data.data.products.edges;
                
                // Render products to the page
                const productsContainer = document.getElementById('products');
                products.forEach(edge => {
                    const product = edge.node;
                    const productDiv = document.createElement('div');
                    productDiv.innerHTML = `
                        <hr>
                        <h2>${product.title}</h2>
                        <p>Price: ${product.priceRange.minVariantPrice.amount} ${product.priceRange.minVariantPrice.currencyCode}</p>
                        <div>${product.descriptionHtml}</div>
                    `;
                    productsContainer.appendChild(productDiv);
                });
            })
            .catch(error => {
                console.error('Error fetching products:', error);
                const productsContainer = document.getElementById('products');
                productsContainer.innerHTML = `<p style="color: red;">Failed to fetch products. Check the console for details.</p>`;
            });
    </script>
</body>
</html>

Open this index.html file in your web browser. Then, open your browser's developer tools (usually by pressing F12 or right-clicking and selecting "Inspect") and look at the Console tab. You should see the JavaScript object containing your product data logged out. The products will also be rendered on the page itself.

This simple example encapsulates the entire process: defining credentials, composing a query, and executing an authenticated POST request using fetch.

Test your understanding!

Modify the productQuery string in your index.html file. Your goal is to also fetch the URL for the first image of each product.

Hint: You'll need to add an images field to your query. Like products, it's a connection that uses edges and node. You'll want to query images(first: 1). Inside the node for the image, you can request the url field. You can use the GraphiQL app to explore and confirm the structure.

After modifying the query, update the HTML rendering logic to display the image using an <img> tag.

Show answer

First, you would update the productQuery constant:

const productQuery = `
    query {
      products(first: 5) {
        edges {
          node {
            id
            title
            handle
            descriptionHtml
            priceRange {
                minVariantPrice {
                    amount
                    currencyCode
                }
            }
            images(first: 1) {
              edges {
                node {
                  url
                  altText
                }
              }
            }
          }
        }
      }
    }
`;

Next, you'd update the rendering logic inside the forEach loop to access this new data and create an <img> element. Note the use of optional chaining (?.) as a good practice in case a product has no image.

// ... inside the .then(data => { ... }) block
products.forEach(edge => {
    const product = edge.node;
    const imageUrl = product.images.edges[0]?.node.url;
    const imageAlt = product.images.edges[0]?.node.altText || product.title;

    const productDiv = document.createElement('div');
    productDiv.innerHTML = `
        <hr>
        <h2>${product.title}</h2>
        ${imageUrl ? `<img src="${imageUrl}" alt="${imageAlt}" width="200">` : ''}
        <p>Price: ${product.priceRange.minVariantPrice.amount} ${product.priceRange.minVariantPrice.currencyCode}</p>
        <div>${product.descriptionHtml}</div>
    `;
    productsContainer.appendChild(productDiv);
});

4. Application in a Modern Stack

While this vanilla JavaScript example is perfect for understanding the core mechanism, in a real project using your skills, you would integrate this logic into your React or Next.js application.

The core fetch call remains the same, but you would typically wrap it in a custom hook (e.g., useShopify) or use a dedicated GraphQL client library like Apollo Client or urql. These libraries provide powerful features like caching, state management, and error handling out of the box.

For a glimpse of how this looks in a Next.js app, the video from Tailwind Labs shows how they wrap the fetch logic into a reusable storefront function—the principle is identical to what we've just done by hand.

Building a Headless Ecommerce Store with Tailwind CSS, Shopify, and Next.js

This clip shows how the fetch logic we just wrote can be abstracted into a reusable helper function within a Next.js application. This connects today's lesson directly to the kind of production code you would write.

Watch from 07:52 to 10:52. Notice how the storefront function they create is just a wrapper around fetch, taking a query and variables as arguments. This is the exact same API call we just made, just organized for a larger project.


Conclusion

Congratulations! You've successfully bridged the gap from theory to practice. You now know how to communicate with Shopify from any client-side application to retrieve data.

Key Takeaways:

  • Authentication is Key: All Storefront API requests must be authenticated with a public X-Shopify-Storefront-Access-Token in the request header.
  • GraphQL is Declarative: You must compose a GraphQL query that explicitly asks for the data fields you need. The GraphiQL app is your best tool for this.
  • The Request is a POST: API calls are sent as POST requests to your store's unique GraphQL endpoint, with the query in the JSON body.
  • fetch is the Foundation: At its core, interacting with the API is a standard HTTP request. Libraries like Apollo Client build on this foundation to add more features, but the underlying principle is the same.

In our next module, we will shift focus back to theming, diving into advanced techniques and performance optimization. You'll learn how to handle large sets of data within a Liquid theme by implementing pagination, a concept that's just as relevant in headless builds when you need to fetch more than just the first few products.