Skip to main content

Welcome to a Grasp lesson

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

Lesson 66

Order Customization Retrieval for Merchants

Recommended resources

Hello again! Let's get started on the final functional piece of our capstone project.

In our last lesson, you did an excellent job wiring up the front-end. Customers can now select their product customizations, and that information is correctly saved to the cart as line item properties using the Ajax API. This is a huge milestone!

But what happens after the customer checks out? The merchant needs to see those customizations to fulfill the order correctly. Today's lesson is about closing that loop. Our learning outcome is to build a backend process to retrieve and display order customizations for the merchant. We will create a new page inside our app where the merchant can see a list of recent orders and their associated custom details.

This will bring together your skills in Node.js, Express.js, and React within the Shopify app ecosystem, demonstrating a complete end-to-end feature.

Where Do Customizations Live After Checkout?

First, let's address a fundamental question: when a customer completes their purchase, where do the line item properties go?

The great news is that Shopify automatically carries them over from the cart to the created Order. The simplest way for a merchant to see this information is directly within the Shopify Admin. If they navigate to an order, the line item properties you saved are displayed right under the product title.

Shopify Order Detail with Custom Line Item Properties
This is the default view a merchant gets in the Shopify Admin. The line item properties we saved in the last lesson are automatically displayed on the order details page, providing the essential information for fulfillment.

So, if the information is already there, why build a custom backend process?

  1. Centralized Information: A professional app provides a "home base" for the merchant. Instead of having to hunt through standard Shopify order pages, they can come to your app's dashboard to see all orders that have customizations managed by your app.
  2. Advanced Filtering & Reporting: What if a merchant wants to see all orders that include an "Engraving"? Or export a list of all engravings for the week? You can only build this kind of value-added functionality by fetching the data and presenting it in your own custom interface.
  3. Integration: Your app's backend could become a hub for sending customization data to other systems—an external engraving service's API, a special label printer, or a custom workflow tool.

Building our own view transforms your project from a simple front-end add-on into a comprehensive merchant tool.


The Backend Strategy: Fetching Orders with GraphQL

Our goal is to create a new page within our app that lists recent orders and their customizations. Since this requires access to store data, this is a job for our Node.js backend and the Admin API.

Here's our plan:

  1. Create a backend API endpoint in our Express.js server (e.g., GET /api/orders).
  2. When this endpoint is called, our server will use the authenticated Shopify API client to send a GraphQL query to fetch recent orders.
  3. We will design the query to specifically request the line items and their custom attributes for each order.
  4. Create a new React page in our front-end that calls our /api/orders endpoint.
  5. Use Shopify's Polaris component library to display the fetched order data in a clean, professional table that matches the Shopify Admin's look and feel.

Your full-stack background with the MERN/Next.js stack makes you perfectly equipped for this task. You're connecting a React front-end to a Node.js API, a pattern you're already very familiar with. The main difference here is that our Node.js backend will, in turn, act as a client to the Shopify GraphQL API.


Step 1: Crafting the GraphQL Query

Before writing any server code, let's design the GraphQL query. We need to ask the Shopify API for a list of orders. The primary query for this is orders.

orders - GraphQL Admin

The Shopify Admin API documentation for the orders query is our reference. It explains how to fetch a list of orders and what arguments are available.

Skim the introduction to understand the purpose of the orders query. Then, look at the 'OrderConnection arguments' like first which we'll use for pagination. Finally, review the 'Possible returns' section to see the edges and nodes structure, which is standard for GraphQL connections.

Each order in the list is an Order object. For each order, we need to dig into its lineItems. And for each line item, we need its custom properties. In the GraphQL Admin API, these are called customAttributes.

Here is the complete query we will use. It fetches the 10 most recent orders and, for each one, includes the line items and their custom attributes.

query GetOrdersWithCustomizations {
  orders(first: 10, sortKey: PROCESSED_AT, reverse: true) {
    edges {
      node {
        id
        name
        lineItems(first: 10) {
          edges {
            node {
              id
              name
              customAttributes {
                key
                value
              }
            }
          }
        }
      }
    }
  }
}

Deconstructing the Query:

  • orders(first: 10, sortKey: PROCESSED_AT, reverse: true): This asks for the first 10 orders, sorted by the most recently processed.
  • edges { node { ... } }: This is the standard GraphQL connection pattern for lists. The actual order data is inside the node.
  • lineItems(first: 10): For each order, we get its list of line items.
  • customAttributes { key value }: This is the crucial part. For each line item, we retrieve the key and value of every custom property attached to it. This is where our "Engraving" text and other customizations will be.

Step 2: Building the Backend Route

Now, let's create the Express.js route in your app's backend that will execute this query. The Shopify CLI app template provides an authenticated shopify object that includes a GraphQL client.

In your project's web/ directory, open the main server file (likely web/index.js). We will add a new route handler.

// Add this somewhere in your web/index.js, after the app setup

app.get("/api/orders", async (req, res) => {
  try {
    // Get the current session from the request.
    // The verifyRequest middleware (already in your template) ensures this is valid.
    const session = res.locals.shopify.session;

    // Create a new GraphQL client from the session.
    const client = new shopify.api.clients.Graphql({ session });

    // The GraphQL query we designed earlier.
    const queryString = `
      query GetOrdersWithCustomizations {
        orders(first: 10, sortKey: PROCESSED_AT, reverse: true) {
          edges {
            node {
              id
              name
              lineItems(first: 10) {
                edges {
                  node {
                    id
                    name
                    customAttributes {
                      key
                      value
                    }
                  }
                }
              }
            }
          }
        }
      }
    `;

    // Make the API call.
    const response = await client.query({
      data: queryString,
    });

    // Send the data back to the front-end.
    // The actual orders are in response.body.data.orders.edges
    res.status(200).send(response.body.data.orders.edges);

  } catch (error) {
    console.error("Failed to process api request:", error);
    res.status(500).send("Internal Server Error");
  }
});

This code snippet creates an endpoint at /api/orders. When your app's front-end makes a request to it, the server securely communicates with Shopify to fetch the order data and then passes it back.


Step 3: Displaying the Data on the Front-End

The final step is to create a new page in our React front-end to display this data. The Shopify CLI template uses file-based routing.

  1. Create a new page file: In your web/frontend/ directory, create a new file: web/frontend/pages/orders.jsx.
  2. Add the page component code: Paste the following React code into your new orders.jsx file. This component uses the useAppQuery hook (provided by the CLI template for authenticated fetches) to call your new backend endpoint and Polaris components to render the UI.
import { Page, Layout, Card, ResourceList, TextStyle, Stack, Spinner } from "@shopify/polaris";
import { useAppQuery } from "../hooks";

export default function OrdersPage() {
  // useAppQuery hook handles fetching, loading, and error states.
  const {
    data: ordersData,
    isLoading,
    isError,
  } = useAppQuery({
    url: "/api/orders",
  });

  // The actual orders are an array of { node: Order } objects.
  const orders = ordersData || [];

  if (isLoading) {
    return (
      <Page>
        <Layout>
          <Layout.Section>
            <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100px' }}>
              <Spinner accessibilityLabel="Loading orders" size="large" />
            </div>
          </Layout.Section>
        </Layout>
      </Page>
    );
  }

  if (isError) {
    return (
        <Page>
            <Layout>
                <Layout.Section>
                    <Card sectioned>
                        <TextStyle variation="negative">Error loading orders.</TextStyle>
                    </Card>
                </Layout.Section>
            </Layout>
        </Page>
    );
  }

  return (
    <Page title="Recent Orders with Customizations">
      <Layout>
        <Layout.Section>
          <Card>
            <ResourceList
              resourceName={{ singular: "order", plural: "orders" }}
              items={orders}
              renderItem={(orderItem) => {
                const { node: order } = orderItem;
                const { id, name, lineItems } = order;

                return (
                  <ResourceList.Item
                    id={id}
                    accessibilityLabel={`View details for order ${name}`}
                  >
                    <h3>
                      <TextStyle variation="strong">{name}</TextStyle>
                    </h3>
                    {lineItems.edges.map(({ node: lineItem }) => (
                      <div key={lineItem.id} style={{ marginLeft: '20px', marginTop: '10px' }}>
                        <div>{lineItem.name}</div>
                        {lineItem.customAttributes.length > 0 && (
                          <Stack vertical spacing="tight">
                            {lineItem.customAttributes.map((attr) => (
                              <div key={attr.key}>
                                <TextStyle variation="subdued">{attr.key}: {attr.value}</TextStyle>
                              </div>
                            ))}
                          </Stack>
                        )}
                      </div>
                    ))}
                  </ResourceList.Item>
                );
              }}
            />
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}
  1. Add a link to the page: To navigate to this new page, open web/frontend/components/App.jsx (or wherever your main navigation is) and add a <Navigation.Item> that points to /orders.

Now, restart your dev server (npm run dev) and navigate to your new "Orders" page in the app. You should see a list of the 10 most recent orders, and under each relevant product, you'll find the customization details neatly listed!

Test your understanding!

A merchant loves the new orders page but has a request: for line items that have no customizations, they want to hide them from the list to reduce clutter. They only want to see the products that were actually customized.

How would you modify the orders.jsx React component to implement this?

Show answer

You would need to filter the lineItems.edges array before mapping over it. Instead of mapping the entire array, you would do something like this:

{lineItems.edges
  .filter(({ node: lineItem }) => lineItem.customAttributes.length > 0) // Add this filter
  .map(({ node: lineItem }) => (
    // ... rest of the rendering logic
  ))
}

This change filters the line items array to include only those items where the customAttributes array is not empty, effectively hiding products without customizations.


Conclusion

Congratulations! You have successfully built a backend process to retrieve and display order customizations for the merchant. This completes the core functionality of your Product Customizer app. You've connected a custom front-end experience for the customer all the way through to a custom backend view for the merchant.

Key Takeaways:

  • Line item properties from the cart are automatically saved to the Order object on checkout.
  • The Admin GraphQL API is the modern, powerful way to query for store data from your app's backend.
  • The orders query allows you to fetch a list of orders, while the lineItems and customAttributes fields let you drill down to the specific customization data.
  • In a Shopify app, you typically create backend API routes that your React front-end calls to securely fetch data.

Next Steps

Your app is now a complete, functional piece of software. It solves a real problem for merchants from end to end. There's just one final, crucial step to making it a real-world tool: deployment.

In our next lesson, we will tackle the final learning outcome: Deploy the full-stack application to a cloud hosting provider (e.g., Fly.io, Heroku). This will take your app from localhost to the public internet, making it ready to be installed on real stores and added to your freelance portfolio.