The package can be installed by adding shopify_api
to your list of dependencies in mix.exs
.
def deps do
[
{:shopify_api, github: "pixelunion/elixir-shopifyapi", tag: "v0.9.3"}
]
end
Add it to your phoenix routes.
scope "/shop" do
forward("/", ShopifyAPI.Router)
end
If you want to be able to handle webhooks you need to add this to your endpoint before the parsers section
plug(ShopifyAPI.Plugs.Webhook, mount: "/shop/webhook")
If you want persisted Apps, Shops, and Tokens add configuration to your functions.
config :shopify_api, ShopifyAPI.AuthTokenServer,
initializer: {MyApp.AuthToken, :init, []},
persistance: {MyApp.AuthToken, :save, []}
config :shopify_api, ShopifyAPI.AppServer,
initializer: {MyApp.ShopifyApp, :init, []},
persistance: {MyApp.ShopifyApp, :save, []}
config :shopify_api, ShopifyAPI.ShopServer,
initializer: {MyApp.Shop, :init, []},
persistance: {MyApp.Shop, :save, []}
Optional, add graphiql to your phoenix routes
if Mix.env == :dev do
forward(
"/graphiql",
to: Absinthe.Plug.GraphiQL,
schema: GraphQL.Config.Schema,
interface: :playground
)
end
There is a boilerplate repo for quickly getting up and running at ShopifyApp
- Start something like ngrok
- Configure your app to allow your ngrok url as one of the redirect_urls
- Point your browser to
http://localhost:4000/shop/install?shop=your-shop.myshopify.com&app=yourapp
and it should prompt you to login and authorize it.
There is a GraphQL interface to get and update configuration, this is the recommended way of pushing configuration in to your server.
Shopify introduced API versioning here: https://help.shopify.com/en/api/versioning
Configure the version to use in your config.exs, it will default to a stable version as ref'd in the request module.
config :shopify_api, ShopifyAPI.REST, api_version: "2019-04"
The ShopifyAPI has three cache servers, App, Shop, and Auth Token. These speed up access to data structures used for interacting with Shopify. A supervisor, ShopifyAPI.CacheSupervisor, is there to help manage start up and maintain all three. Add the CacheSupervisor to your application start up and define some hooks for preloading data.
NOTE: Make sure you start the services or supervisor after services that are using in preloading the data. (ie Ecto)
Add the following to your application:
def start(_type, _args) do
# Define workers and child supervisors to be supervised
children = [
MyApp.Repo,
ShopifyAPI.CacheSupervisor
]
Supervisor.start_link(children, strategy: :one_for_one)
end
example fetch:
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{"query": "{ allShops { domain } }"}' \
http://localhost:4000/shop/graphql/config
example set:
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{"query": "mutation M { updateShop(domain: \"<STORE-DOMAIN>\",) { domain } }" }' \
http://localhost:4000/shop/graphql/config
example fetch:
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{"query": "{ allApps { authRedirectUri, clientId, clientSecret, name, nonce, scope } }"}' \
http://localhost:4000/shop/graphql/config
example set:
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{"query": "mutation M { updateApp(authRedirectUri: \"<REDIRECT-URI>\", clientId: <ID>, clientSecret: \"<SECRET>\", name: \"<APP-NAME>\", nonce: \"<NONCE>\", scope: \"<APP-SCOPE>\") { name } }" }' \
http://localhost:4000/shop/graphql/config
example fetch:
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{"query": "{ allAuthTokens { appName, shopName, token, timestamp, code } }"}' \
http://localhost:4000/shop/graphql/config
example set:
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{"query": "mutation M { updateAuthToken(token: \"<TOKEN>\", timestamp: <TIMESTAMP>, shopName: \"<SHOPIFY-STORE-DOMAIN>\", code: \"<RESPONSE-CODE>\", appName: \"<APP-NAME>\") { appName } }" }' \
http://localhost:4000/shop/graphql/config
Setting up webhook handling requires adding a handler to your configuration.
config :shopify_api, webhook_filter: {MyApp.WebhookFilter, :process, []}
config :shopify_api, ShopifyAPI.Webhook, uri: "https://testapp.ngrok.io/shop/webhook"
A handler will need to be created
defmodule MyApp.WebhookFilter do
def process(%{action: "orders/create", object: %{}} = event) do
IO.inspect(event, label: event)
# ....
end
end
And finally webhooks will have to be registered with Shopify. After installing a shop you will need to fire a webhook creation.
token = ShopifyAPI.AuthTokenServer.get("shop domain", "app name")
topic = "orders/create"
server_address = ShopifyAPI.REST.Webhook.webhook_uri(token)
webhook = %{topic: topic, address: server_address}
ShopifyAPI.REST.Webhook.create(token, %{webhook: webhook})
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/shopify_api.
GraphQL
implementation handles GraphQL Queries against Shopify API using HTTPoison library as client, this initial implementation consists of hitting Shopify GraphQL and returning the response in a tuple {:ok, %Response{}} | {:error, %Response{}}
containing the response and metadata(actualQueryCost, throttleStatus).
Configure the version to use in your config.exs, it will default to a stable version as ref'd in the graphql module.
config :shopify_api, ShopifyAPI.GraphQL, graphql_version: "2019-07"
Because GraphQL responses
can be a little complex we are parsing/wraping responses %HTTPoison.response
to %GraphQL.Response
.
Successful response:
{:ok, %ShopifyAPI.GraphQL.Response{response: %{}, metadata: %{}, status_code: code}}
Failed response:
{:error, %HTTPoison.Response{}}
The shopify_api
library will emit events using the :telemetry
library. Consumers of shopify_api
can then use these events for customized metrics aggregation and more.
The following telemetry events are generated:
[:shopify_api, :rest_request, :success]
[:shopify_api, :rest_request, :failure]
[:shopify_api, :throttling, :over_limit]
[:shopify_api, :throttling, :within_limit]
[:shopify_api, :graphql_request, :success]
[:shopify_api, :graphql_request, :failure]
[:shopify_api, :bulk_operation, :success]
[:shopify_api, :bulk_operation, :failure]
As an example, you could use an external module to instrument API requests made by shopify_api
:
defmodule Instrumenter do
def setup do
events = [
[:shopify_api, :rest_request, :success],
[:shopify_api, :rest_request, :failure]
]
:telemetry.attach_many("my-instrumenter", events, &handle_event/4, nil)
end
def handle_event([:shopify_api, :rest_request, :success], measurements, metadata, _config) do
# Ship success events
end
end```