- Installation
- Setup
- Calling the API
- OAuth
- Cookieless authentication
- Webhooks
- Verification
- Mixins
- Testing
Add the gem to your 'Gemfile':
gem 'shopify-client'
Warning: There are currently no API stability guarantees. I am primarily building this gem for use in my own projects, and break things as needed.
ShopifyClient.configure do |config|
config.api_key = '...'
config.api_version = '...' # e.g. '2021-04'
config.cache_ttl = 3600
config.redirect_uri = '...' # for OAuth
config.logger = Logger.new($stdout) # defaults to a null logger
config.scope = '...'
config.shared_secret = '...'
config.webhook_uri = '...'
end
All settings are optional and in some private apps, you may not require any configuration at all.
client = ShopifyClient::Client.new(myshopify_domain, access_token)
client.get('orders', since_id: since_id).data['orders']
client.post('orders', order: new_order)
client.graphql(%(
{
orders(first: 10) {
edges {
node {
id
tags
}
}
}
}
)).data['data']['orders']
Request logging is disabled by default. To enable it:
ShopifyClient.config.logger = Logger.new($stdout)
Request throttling is enabled by default. If you're using Redis, throttling will automatically make use of it; otherwise, throttling will only be maintained across a single thread.
The gem wraps Shopify's bulk query API by writing the result to a temporary file and yielding an enumerator which itself streams each line of the result to limit memory usage.
client.graphql_bulk(%(
{
products {
edges {
node {
id
handle
}
}
}
}
)) do |lines|
db.transaction do
lines.each do |product|
db[:products].insert(
id: product['id'],
handle: product['handle'],
)
end
end
end
Bulk requests are limited to one per shop at any one time. Creating a new bulk request via the gem will cancel any request in progress for the shop.
Make a cached GET request:
client.get_cached('orders', params: {since_id: since_id})
Note that unlike #get
, #get_cached
returns the Response#data
hash rather
than a Response
object.
Making the same call with the same shop/client, will result in the data being returned straight from the cache on subsequent calls, until the configured TTL expires (the default TTL is 1 hour). If you're using Redis, it will be used as the cache store; otherwise, the cache will be stored in a thread local variable.
You can also manually build and clear a cached request. For example, you might need to clear the cache without waiting for the TTL if you receive an update webhook indicating that the cached data is obsolete:
get_shop = ShopifyClient::CachedRequest.new('shop')
# Request shop data (from API).
get_shop.(client)
# Request shop data (from cache).
get_shop.(client)
Clear the cache data to force fetch from API on next access:
get_shop.clear(myshopify_domain)
Set the cache data (e.g. from shop/update webhook body):
get_shop.set(myshopify_domain, new_shop)
When you make a GET request, you can request the next or the previous page directly from the response object.
page_1 = client.get('orders')
page_2 = page_1.next_page
page_1 = page_2.previous_page
When no page is available, nil
will be returned.
Redirect unauthorised users through the Shopify OAuth flow:
authorise = ShopifyClient::Authorise.new
redirect_to authorise.authorisation_code_url(myshopify_domain)
Once the user returns to the app, exchange the authorisation code for an access token:
access_token = authorise.(client, authorisation_code)
Embedded apps using App Bridge are required to use the cookieless authentication system which uses JWT session tokens rather than cookies to authenticate users signed into the Shopify admin.
In config.ru, or wherever you set up your middleware stack:
use ShopifyClient::Cookieless::Middleware
You can also control when session tokens are checked with a predicate (such as only for certain paths):
use ShopifyClient::Cookieless::Middleware, is_authenticated: ->(env) do
# ...
end
You can also check the Authorization header manually, if you require more control than the middleware provides:
begin
ShopifyClient::Cookieless::CheckHeader.new.(env)
rescue ShopifyClient::Error => e
# ...
end
Configure each webhook the app will create (if any), and register handlers:
ShopifyClient.webhooks.register('orders/create', OrdersCreateWebhook.new, fields: %w[id tags])
You can register as many handlers as you need for a topic, and the gem will merge required fields across all handlers when creating the webhooks.
To call/delegate a webhook to its handler for processing, you will likely want to create a worker around something like this:
webhook = ShopifyClient::Webhook.new(myshopify_domain, topic, data)
ShopifyClient.webhooks.delegate(webhook)
Create/delete all configured webhooks (see above):
ShopifyClient::CreateAllWebhooks.new.(client)
ShopifyClient::DeleteAllWebhooks.new.(client)
Create/delete webhooks manually:
webhook = {topic: 'orders/create', fields: %w[id tags]}
ShopifyClient::CreateWebhook.new.(client, webhook)
ShopifyClient::DeleteWebhook.new.(client, webhook_id)
Verify callback requests with the request params:
begin
ShopifyClient::VerifyRequest.new.(params)
rescue ShopifyClient::Error => e
# ...
end
Verify webhook requests with the request data and the HMAC header:
begin
ShopifyClient::VerifyWebhook.new.(data, hmac)
rescue ShopifyClient::Error => e
# ...
end
A set of mixins is provided for easily creating repository classes for API resources. Each mixin represents an operation or a set of operations, e.g. reading and writing data to/from the API.
class OrderRepository
include ShopifyClient::Resource::Read
resource :orders
default_params fields: 'id,tags', limit: 250
end
order_repo = OrderRepository.new
Find a single result:
order_repo.find_by_id(client, id)
Iterate over results (automatic pagination):
order_repo.all(client).each do |order|
# ...
end
class OrderRepository
include ShopifyClient::Resource::Create
resource :orders
end
order_repo = OrderRepository.new
order_repo.create(client, new_order)
class OrderRepository
include ShopifyClient::Resource::Update
resource :orders
end
order_repo = OrderRepository.new
order_repo.update(client, id, order)
class OrderRepository
include ShopifyClient::Resource::Delete
resource :orders
end
order_repo = OrderRepository.new
order_repo.delete(client, id)
The integration tests require a private app with the scope write_products
.
Create a .env file specifying the test shop, private app password, and a valid
webhook URI:
TEST_SHOP='test-shop.myshopify.com'
TEST_PASSWORD='shppa_...'
TEST_WEBHOOK_URI='https://.../webhooks'
Run the suite:
$ bundle exec rake test:integration