The project is in a healthy, maintained state
Small class to simplify the writing and handling of GraphQL queries and mutations for the Shopify Admin API. Comes with built-in retry, pagination, error handling, and more!
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

>= 13.0
~> 3.0

Runtime

 Project Readme

ShopifyAPI::GraphQL::Request

Small class to simplify the writing and handling of GraphQL queries and mutations for the Shopify Admin API. Comes with built-in retry, pagination, error handling, and more!

Usage

It's recommended to organize queries and mutations by subclassing ShopifyAPI::GraphQL::Request:

require "shopify_api/graphql/request"

class ShopifyProduct < ShopifyAPI::GraphQL::Request
  # Define your queries/mutations
  FIND =<<-GQL
    query($id: ID!) {
      product(id: $id) {
        id
        title
        descriptionHtml
      }
    }
  GQL

  UPDATE =<<-GQL
    mutation($product: ProductUpdateInput!) {
      productUpdate(product: $product) {
        product {
          id
          title
          descriptionHtml
        }
        userErrors {
          field
          message
        }
      }
    }
  GQL

  LIST <<-GQL
    query($after: String) {
      products(first: 25 after: $after) {
        pageInfo {
          hasNextPage
          endCursor
        }
        edges {
          node {
            id
            title
            descriptionHtml
          }
        }
      }
    }
  GQL

  def find(id)
    execute(FIND, :id => gid::Product(id)).dig(:data, :product)
  end

  def update(id, changes)
    execute(UPDATE, :product => changes.merge(:id => gid::Product(id))).dig(:data, :product_update, :product)
  end

  def list
    paginate(LIST) { |page| yield page.dig(:data, :products, :edges) }
  end
end

Then use it:

shopify = ShopifyProduct.new("a-shop", token)

begin
  product = shopify.find(123)
  p product[:id]
  p product[:description_html]
rescue ShopifyAPI::GraphQL::Request::NotFoundError => e
  warn "Product not found: #{e}"
end

begin
  product = shopify.update(123, :description_html => "Something amaaaazing!")
  p product[:id]
  p product[:description_html]
rescue ShopifyAPI::GraphQL::Request::UserError => e
  warn "User errors:"
  e.errors { |err| warn err["field"] }
end

begin
  shopify.list(123) do |node|
    product = node[:node]
    p product[:id]
    p product[:description_html]
  end
rescue ShopifyAPI::GraphQL::Request::NotFoundError => e
  warn "Request failed: #{e}"
end

Subclasses have access to the following methods:

  • #execute - Execute a query or mutation with the provided arguments
  • #paginate - Execute a query with pagination; without a block a lazy enumerator (Enumerator::Lazy) is returned
  • #gid - Used for Global ID manipulation (an instance of TinyGID)
  • #gql - The underlying GraphQL client (an instance of ShopifyAPI::GraphQL::Tiny)

#execute and #paginate also:

  • Automatically retry failed or rate-limited requests
  • Accept snake_case Symbol keys
  • Return a Hash with snake_case Symbol keys
  • Raise a UserError when a mutation's response contains userErrors
  • Raise a NotFoundError when a query's result cannot be found

Both of these are small wrappers around the equivalent methods on ShopifyAPI::GraphQL::Tiny. For more information see its documentation on retries.

With the exception of retry, most defaults can be disabled per instance or per execution:

class ShopifyProduct < ShopifyAPI::GraphQL::Request
  def initialize(shop, token)
    super(shop, token, :raise_if_not_found => false, :raise_if_user_errors => false, :snake_case => false)
  end

  def find(gid)
    execute(QUERY, :raise_if_not_found => true)
  end
end

Disabling retry must be done per instance.

Setting the GraphQL API Version

Pass the desired version to Request's constructor:

class ShopifyProduct < ShopifyAPI::GraphQL::Request
  def initialize(shop, token)
    super(shop, token, :version => "2026-01")
  end
end

Making Requests Without Subclassing

Of course you can make requests directly on an instance of ShopifyAPI::GraphQL::Request:

require "shopify_api/graphql/request"

request = ShopifyAPI::GraphQL::Request.new("a-shop", token)

begin
  product = request.execute(query, :id => "gid://shopify/Product/123").dig(:data, :product)
  p product[:title]
  p product[:description_html]
rescue ShopifyAPI::GraphQL::Request::NotFoundError => e
  p e
end

And mutations:

begin
  product = request.execute(mutation, :id => "gid://shopify/Product/123", :title => "Foo Hoo!").dig(:data, :product)
rescue ShopifyAPI::GraphQL::Request::UserError => e
  p e
end

More Info

For more information checkout the API docs

Why Use This Instead of Shopify's API Client?

  • Easy-to-use
  • Built-in retry
  • Built-in pagination
  • Improved exception handling
  • You can use :snake_case hash keys
  • Lightweight

Overall, Shopify's API client is bloated trash that will give you development headaches and long-term maintenance nightmares.

We used to use it, staring way back in 2015, but eventually had to pivot away from their Ruby libraries due to developer frustration and high maintenance cost (and don't get us started on the ShopifyApp gem!@#).

For more information see: Shopify/shopify-api-ruby#1181

Testing

cp env.template .env and fill-in .env with the missing values. This requires a Shopify store.

See Also

  • Shopify Dev Tools - Command-line program to assist with the development and/or maintenance of Shopify apps and stores
  • Shopify ID Export - Dump Shopify product and variant IDs —along with other identifiers— to a CSV or JSON file
  • ShopifyAPI::GraphQL::Tiny - Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in pagination and retry
  • TinyGID - Build Global ID (gid://) URI strings from scalar values

License

The gem is available as open source under the terms of the MIT License.


Made by ScreenStaring