Skip to main content

Welcome to a Grasp lesson

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

Lesson 53

Connecting Node.js Backend to Polaris Frontend

Recommended resources

Hello again! Welcome to our next lesson.

In our last session, we mastered how to make our embedded app communicate with the Shopify Admin, using App Bridge to trigger native UI elements like modals and toast messages. This covered the "outbound" communication from your app to its host.

Today, we'll tackle the reverse and arguably most crucial data flow: fetching data from your app's backend to display within your Polaris front-end. This is the heart of building a dynamic, data-driven Shopify app. Your experience with full-stack development will be a great asset here, as we'll be connecting a React front-end to a Node.js API, a pattern you're already familiar with, but within the specific context of Shopify's authenticated environment.

By the end of this lesson, you will be able to fetch data from your app's backend (Node.js) to populate the Polaris front-end.

Why Have a Backend at All?

Following your "first principles" approach, it's natural to ask: "Why can't my React app just call the Shopify Admin API directly?"

You could, but it's not a recommended or secure practice for a public app. The primary reasons for using your own backend as a middleman are:

  1. Security: Your app's API secret key should never be exposed on the client-side. The backend acts as a secure proxy, holding the credentials and making authenticated requests to Shopify on your app's behalf.
  2. Complex Logic: The backend is the perfect place to run complex business logic, process data from multiple sources, or perform intensive computations without slowing down the user's browser.
  3. Data Persistence: If your app needs its own database to store custom data (which we'll cover later), the backend is where that database connection lives and where you'll define the logic to read from and write to it.

The goal is to create a UI that is seamlessly integrated into the Shopify Admin, populated with rich data managed by your secure backend.

Shopify Embedded App 'PikUp' Displaying a Product Picklist
This example shows an embedded app, 'PikUp', that has fetched product and order data from its backend and is displaying it in a clean Polaris list within the Shopify admin.

The Full-Stack Data Flow

The typical data flow for an embedded Shopify app looks like this:

  1. A React component in your front-end needs data to render.
  2. It makes a fetch request to an API endpoint on your Node.js backend (e.g., /api/products). This request is automatically authenticated by Shopify's libraries.
  3. Your backend endpoint receives the request, verifies the session, and uses the Shopify API library to fetch the necessary data from the store (e.g., a list of products).
  4. The backend can process or combine this data before sending it back to the front-end as a JSON response.
  5. The React component receives the JSON, updates its state, and re-renders the Polaris components with the new data.

Let's break this down step-by-step.


Step 1: Create a Backend API Endpoint

First, you need to expose an API route in your backend server that your front-end can call. In the Shopify app template, this is typically done in the web/index.js file (for the Node.js template).

You'll create a standard Express.js route. The magic happens inside the route handler, where you can access an authenticated Shopify API client from the request context.

A detailed guide to create an AI Shopify App - Step by Step

The article 'A detailed guide to create an AI Shopify App' provides a clear and concise example of creating a backend API route to fetch products. This is an excellent starting point.

Please read the section titled '1. create api route to get product in index.js'. This shows a simple app.get("/api/products", ...) route that uses the REST Admin API to fetch all products.

Here is a typical implementation using the Shopify CLI template structure. Notice how res.locals.shopify.session is used to provide the necessary credentials for the API call.

// In web/index.js or a dedicated routes file

// ... other imports
import { productCreator } from "./product-creator.js"; // Example of product creation logic

// ... other middleware

// A new GET endpoint to fetch all products
app.get("/api/products", async (req, res) => {
  try {
    const data = await shopify.api.rest.Product.all({
      session: res.locals.shopify.session,
    });
    res.status(200).send(data);
  } catch (error) {
    console.error("Failed to fetch products:", error);
    res.status(500).send("Error fetching products");
  }
});

// ... the rest of your server setup

Step 2: Fetch Data from the Front-end

With the backend endpoint ready, the front-end needs to call it. To handle authentication seamlessly, the Shopify app template provides a custom React hook: useAuthenticatedFetch.

This hook is a wrapper around the browser's standard fetch API. It automatically attaches the necessary session token to the request, ensuring your backend recognizes it as a legitimate, authenticated call from the Shopify Admin.

How to make a Shopify app

This video, though using a custom boilerplate, gives an excellent explanation of the purpose of an authenticated fetch utility, which is conceptually identical to the useAuthenticatedFetch hook in the standard CLI template.

First, watch the brief overview of the client-side app from 00:44:12 to 00:46:20, where the narrator introduces the userLoggedInFetch utility. Then, watch the demonstration from 00:48:05 to 00:50:00, which shows how this utility is used to make authenticated GET, POST, and GraphQL requests to the backend. This is the core pattern we will be implementing.

In a React component, you would typically use this hook inside a useEffect to fetch data when the component mounts.

// In a frontend component, e.g., web/frontend/pages/ProductList.jsx
import { useState, useEffect } from "react";
import { useAuthenticatedFetch } from "@shopify/app-bridge-react";
import { Page, Layout, Card, SkeletonBodyText } from "@shopify/polaris";

export default function ProductListPage() {
  const [products, setProducts] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const fetch = useAuthenticatedFetch(); // Get the authenticated fetch function

  useEffect(() => {
    const fetchProducts = async () => {
      setIsLoading(true);
      const response = await fetch("/api/products"); // Call our backend endpoint
      const { data } = await response.json();
      setProducts(data);
      setIsLoading(false);
    };

    fetchProducts();
  }, [fetch]); // Dependency array includes fetch

  if (isLoading) {
    return (
      <Page>
        <Layout>
          <Layout.Section>
            <Card sectioned>
              <SkeletonBodyText lines={5} />
            </Card>
          </Layout.Section>
        </Layout>
      </Page>
    );
  }

  // We'll render the product list in the next step
  return (
    <Page title="Product List">
      <Layout>
        <Layout.Section>
          {products.map((product) => (
            <Card key={product.id} sectioned>
              {product.title}
            </Card>
          ))}
        </Layout.Section>
      </Layout>
    </Page>
  );
}
Test your understanding!

In the useEffect code above, why is the fetch function from useAuthenticatedFetch included in the dependency array?

Show answer

The fetch function returned by useAuthenticatedFetch is guaranteed by the library to be stable and not change on re-renders. However, the React ESLint rule react-hooks/exhaustive-deps correctly identifies that fetch is a dependency from outside the useEffect scope. Including it in the dependency array satisfies this rule and is the correct practice, even if in this specific case the function's reference doesn't change. It ensures the effect would re-run if the fetch function were to change.


An Alternative Pattern: Remix Loaders

The current Shopify CLI generates apps using the Remix framework. Remix introduces a powerful pattern that simplifies data fetching. Instead of using useEffect, you define a loader function that runs on the server before your page component renders.

The loader function fetches the data and returns it. Then, in your component, you simply call the useLoaderData hook to access it. This pattern is cleaner for initial data loading and is the idiomatic way to do it in a modern Shopify app.

Getting Started with Shopify App Bridge - Create a Shopify App

The video 'Getting Started with Shopify App Bridge' demonstrates this exact server-side loader pattern. It's a fantastic walkthrough of the entire process, from creating the loader to rendering the data.

Watch the section from 17:20 to 26:15. It shows how to: Create a loader function in a Remix route file. Use authenticate.admin to get a GraphQL client on the backend. Write a GraphQL query to fetch product data. Update the app's access scopes to get permission for products. Return the data as JSON from the loader. Use the useLoaderData hook in the React component to access the data.

For a text-based reference that shows a similar pattern using React Router (another popular framework), the Shopify developer documentation has a great tutorial.

Build a Shopify app using React Router

This Shopify tutorial on building an app shows how to load and list data on the app's index page using a loader function.

Read the sections titled 'List QR codes' and 'Load QR codes'. This reinforces the concept of a loader that runs on the server to fetch data, which is then made available to the front-end component.


Step 3: Render the Data with Polaris

Once your data is available in your component's state (or from useLoaderData), the final step is to render it using Polaris components. For lists of data, the ResourceList component is purpose-built and provides a professional look that matches the Shopify Admin.

Getting Started with Shopify App Bridge - Create a Shopify App

Let's finish the 'Getting Started with Shopify App Bridge' video, which perfectly demonstrates how to take the fetched product data and render it in a ResourceList.

Watch from 27:47 to the end. Pay close attention to how the items prop of ResourceList is populated and how the renderItem function is used to define the layout for each individual product in the list.

This final step brings everything together, displaying the data fetched from your backend in a user-friendly and familiar interface.


Conclusion

In this lesson, we connected the two halves of your application, establishing the crucial data flow from your Node.js backend to your Polaris front-end. You are now equipped to build fully-featured embedded app pages that can display any data from a merchant's store.

Key Takeaways:

  • A Shopify app's backend is essential for security and for running complex logic.
  • The data flow is Frontend -> Backend API -> Shopify API -> Backend -> Frontend.
  • You create standard API endpoints in your Node.js/Express server to serve data.
  • The useAuthenticatedFetch hook is used in the front-end to make secure, authenticated requests to your backend.
  • In modern Shopify apps using Remix, the loader function and useLoaderData hook provide a cleaner pattern for fetching and accessing data.
  • Polaris components like ResourceList are used to present the fetched data in a structured and professional UI.

In our next lesson, we will explore Theme App Extensions. This is how your app can break out of the admin <iframe> and inject functionality, UI, and data directly onto the merchant's live storefront, a critical skill for many freelance projects.