Migrating from REST to GraphQL

In this section we’ll take a look at how certain concepts are handled in GraphQL.

Definitions Primer

Nodes

Nodes represent objects such as Order, and contain data in fields.

Edges

Edges are links between two related nodes. These allow you to make nested queries, gathering information from multiple nodes by traversing the edges in a single GraphQL call.

Connections

Connections are lists of similar edges. These let you refer to and access sets of edges related to a node such as orders on a shop.

Cursors

Cursors are a unique reference to an node’s position in a connection. This lets you query for results from a starting point, enabling efficient pagination. This is similar to REST’s since_id parameter.

IDs

With the introduction of new ways to access the same object, it's important to clarify how and where to use these different IDs.

Translating from REST IDs to GraphQL IDs

To help with migrating from our REST to the GraphQL, REST responses now include the GraphQL Admin API ID field, admin_graphql_api_id,. The ID in this field can be used to query the object directly using the GraphQL Admin API.

The following example queries a product variant in REST, and then uses the admin_graphql_api_id to query it in GraphQL.

REST Endpoint

GET /admin/products/1321540747320/variants/12195007594552.json

REST Response

{
    "variant": {
        "id": 12195007594552,
        "product_id": 1321540747320,
        "title": "Default Title",
        "...": "...",
        "admin_graphql_api_id": "gid://shopify/ProductVariant/12195007594552"
    }
}

GraphQL Request using admin_graphql_api_id

{
    productVariant (id: "gid://shopify/ProductVariant/12195007594552") {
        id
        title
    }
}

GraphQL Response

{
    "data": {
        "productVariant": {
            "id": "gid://shopify/ProductVariant/12195007594552",
            "title": "Default Title"
        }
    },
    ...
}

GraphQL IDs are opaque

GraphQL ID generation is implementation dependent, and as such does not follow any convention other than being a URI. There is no guarantee that GraphQL IDs will follow a similar "structure" (gid -> shopify -> resource -> rest_id) as the previous example, and therefore you should avoid generating IDs manually.

The following example shows how admin_graphql_api_id does not follow an expected structure:

REST endpoint

GET /admin/inventory_levels.json?inventory_item_ids=12261979488312

REST Response

{
    "inventory_levels": [
        {
            "inventory_item_id": 12261979488312,
            "location_id": 6884556842,
            "available": 5,
            "updated_at": "2018-05-17T12:58:30-04:00",
            "admin_graphql_api_id": "gid://shopify/InventoryLevel/6485147690?inventory_item_id=12261979488312"
        },
        {
            "inventory_item_id": 12261979488312,
            "location_id": 13968834616,
            "available": 7,
            "updated_at": "2018-05-17T12:58:35-04:00",
            "admin_graphql_api_id": "gid://shopify/InventoryLevel/13570506808?inventory_item_id=12261979488312"
        },
        {
            "inventory_item_id": 12261979488312,
            "location_id": 13968867384,
            "available": 9,
            "updated_at": "2018-05-17T12:58:35-04:00",
            "admin_graphql_api_id": "gid://shopify/InventoryLevel/13570539576?inventory_item_id=12261979488312"
        }
    ]
}

Always treat the admin_graphql_api_id string as an opaque ID.

Storefront API IDs

The Storefront API is also queried using GraphQL, and uses its own separate IDs.

The GraphQL Admin API provides the storefrontId field which enables you to get that object's Storefront API ID.

The following example shows how to request the storefrontId field on a product.

GraphQL Request

query {
  product(id:"gid://shopify/Product/1324932956216") {
    id
    title
    storefrontId
  }
}

GraphQL Response

{
  "data": {
    "product": {
      "id": "gid://shopify/Product/1324932956216",
      "title": "GQL Lego Connector",
      "storefrontId": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzEzMjQ5MzI5NTYyMTY="
    }
  },
  ...
}

Nested Queries

Using connections, you can get information from related objects with a single request. For example, in the case of an order, you might want to know the total price, the customer's name, metafields, as well as the title of other variants belonging to the product in the order.

Using REST, we'd query the following endpoints and filter excess information.

  • /admin/orders/{order_id}.json
  • /admin/products/{product_id}/variants.json
  • /admin/customers/{customer_id}/metafields.json

Using GraphQL, you can make a single request using connections to get the desired data.

query {
  order(id:"gid://shopify/Order/410487128120") {
    id
    totalPrice
    customer {
      metafields (first:10) {
        edges {
          node {
            description
          }
        }
      }
    }
    lineItems (first:10) {
      edges {
        node {
          variant {
            product {
              variants(first:10) {
                edges {
                  node {
                    title
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Pagination

In REST, you paginate via the query parameters. Using limit, you can split the total entries into manageable groups and paginate through them with page.

/admin/products.json?limit=1&page=2

In GraphQL, you can apply similar concepts with cursor based pagination. When using connections, you'll want to provide the first or last argument, which specifies the number of items you want returned. This is equivalent to limit.

Requesting the cursor field lets you get a reference to the position of a node within the connections. You can use that reference to obtain the next set of items. This is equivalent to page, but with more precision, robustness, and flexibility.

Finally, request the pagInfo field to determine if there are any more pages to request. The nested fields “hasNextPage” and “hasPreviousPage” are boolean fields which let us know if we'll be able to paginate forwards or backwards. If both are false, there are no more results or pages.

Tying it all together

This example query explores the connection between shops and orders. In our request, we’ll explore the connection between shops and orders, and how we can get only the fields we’re interested in.

Note

You can use your own app to submit these queries, or use Shopify GraphiQL App.

This query asks for the first three elements on the orders connection. On each of these orders, the id, name, and email fields are requested. You request the cursor and pageInfo fields to handle pagination.

Request

query {
  orders(first: 3) {
    edges {
      cursor
      node {
        id
        name
        email
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

Response

"data": {
  "orders": {
    "edges": [
      {
        "cursor": "eyJsYXN0X2lkIjo0MTA0Nzk0OTMxNzYsImxhc3RfdmFsdWUiOiIyMDE3LTA0LTMwIDA0OjQ2OjA3In0=",
        "node": {
          "id": "gid://shopify/Order/410479493176",
          "name": "#1592",
          "email": "Maya.Schinner@developer-tools.shopifyapps.com"
        }
      },
      {
        "cursor": "eyJsYXN0X2lkIjo0MTA0Nzg1NDI5MDQsImxhc3RfdmFsdWUiOiIyMDE3LTA1LTAyIDA4OjU5OjM4In0=",
        "node": {
          "id": "gid://shopify/Order/410478542904",
          "name": "#1564",
          "email": "Verda.Fritsch@developer-tools.shopifyapps.com"
        }
      },
      {
        "cursor": "eyJsYXN0X2lkIjo0MTA0ODA5Njc3MzYsImxhc3RfdmFsdWUiOiIyMDE3LTA1LTAyIDE1OjQyOjQ3In0=",
        "node": {
          "id": "gid://shopify/Order/410480967736",
          "name": "#1633",
          "email": "Gino.Davis@developer-tools.shopifyapps.com"
        }
      }],
    "pageInfo": {
      "hasNextPage": true
    }
  }
}

The query returns the data for the first three orders. In order to get the next 3 orders, we’ll have to add the cursor value to our connection arguments.

The data returned indicates that each node has a cursor. Using the last cursor that was returned allows you to continue from that point.

Request

query {
  orders(first: 3, after: "eyJsYXN0X2lkIjo0MTA0ODA5Njc3MzYsImxhc3RfdmFsdWUiOiIyMDE3LTA1LTAyIDE1OjQyOjQ3In0=") {
    edges {
      cursor
      node {
        id
        name
        email
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

Response

"data": {
  "orders": {
    "edges": [
      {
        "cursor": "eyJsYXN0X2lkIjo0MTA0ODU1ODgwMjQsImxhc3RfdmFsdWUiOiIyMDE3LTA1LTA0IDAxOjI3OjAxIn0=",
        "node": {
          "id": "gid://shopify/Order/410485588024",
          "name": "#1768",
          "email": "Gino.Davis@developer-tools.shopifyapps.com"
        }
      },
      {
        "cursor": "eyJsYXN0X2lkIjo0MTA0ODY3Njc2NzIsImxhc3RfdmFsdWUiOiIyMDE3LTA1LTA0IDEzOjQ1OjM2In0=",
        "node": {
          "id": "gid://shopify/Order/410486767672",
          "name": "#1799",
          "email": "Chanel.Auer@developer-tools.shopifyapps.com"
        }
      },
      {
        "cursor": "eyJsYXN0X2lkIjo0MTA0Nzg4Mzc4MTYsImxhc3RfdmFsdWUiOiIyMDE3LTA1LTA0IDIwOjU1OjQ5In0=",
        "node": {
          "id": "gid://shopify/Order/410478837816",
          "name": "#1573",
          "email": "Harmon.Bernhard@developer-tools.shopifyapps.com"
        }
      }],
    "pageInfo": {
      "hasNextPage": true
    }
  }
}

Continue paginating using the same method until hasNextPage returns false.