Shopifyカスタムアプリを作成!Ruby on Rails、ReactでShopify App CLIを使って開発する方法を丁寧に解説!

ShopifyカスタムアプリをRuby on Rails, Reactでの開発方法を徹底解説します。

対象読者は、

  • Ruby on Railsの知識がある人
  • Reactの知識がある人

です。

Shopifyカスタムアプリの開発は、APIドキュメントを精査するだけでかなりの時間がかかってしまいます。

ざっと開発のやり方を知りたい人に、ポイントを抑えたカスタムアプリの構築方法をまとめました。

開発の参考になればと思います。

アプリ開発環境

macOS Big Sur 11.0.1

Ruby 2.6.3

Ruby on Rails 6.0.3

react-rails 2.6.1

アプリ開発準備

Shopify CLIのインストール

まずはHomebrewをインストールしましょう。

HomebrewとはmacOS上で動くパッケージ管理ツールのことです。ターミナルに下記のコマンドを入力してインストールします。

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

Shopify CLIをインストールしましょう。

先ほどインストールしたHomebrewを使ってインストールします。

Page not found · GitHub Pages
$ brew tap shopify/shopify
$ brew install shopify-cli

他にも、debian(apt)またはRubyGemsでインストールできますのでご紹介します。

どの方法でインストールしても問題ありません。

debian(apt)

事前に、Shopify cliをダウンロードし展開します。下記のURLから最新の「shopify-cli-x.y.z.deb」ファイルをダウンロードします。

Releases · Shopify/shopify-cli
Shopify CLI helps you build against the Shopify platform faster. - Shopify/shopify-cli

ダウンロードしたファイルをターミナルコマンドからインストールします。

x.y.zの部分は、ご自身でダウンロードしたバージョンに書き換えてください。

$ sudo apt install /path/to/downloaded/shopify-cli-x.y.z.deb
RubyGems

もしRubyプログラマーなら、RubyGemsを使うと簡単にインストールできます。ここでは、Rubyのインストール方法についての解説は行いません。

Shopify cliのインストールは下記のコマンドで実行できます。

$ sudo gem install shopify-cli

Shopify アプリ CLIが正しくインストールされていることを確認しましょう。

$ shopify version
=> 1.4.1
$ shopify help

で、shopify アプリ CLIのコマンドを参照できます。各コマンドがどのような機能を持つかは下記のコマンドで確認できます。

$ shopify help <command>

※下記のメッセージが表示されることがあります。

would you like to enable anonymous usage reporting?
If you select “Yes”, we’ll collect data about which commands you use and which errors you encounter.
Sharing this anonymous data helps Shopify improve this tool.

エラーなどをShopifyに共有しても良いかというメッセージです。

特に気にしない方は、Yesで問題ありません。

PostgreSQLのインストール

次にデータベースをインストールします。

今回データベースはPostgreSQLを利用するため、Homebrewからインストールしておきます。

すでにインストール済みであれば必要ありません。

$ brew install postgresql
$ brew services start postgresql
(postgresqlを起動させます)

これで構築の準備は完了です。

もし、ここまでの工程でエラーが発生してしまった場合は、開発環境や発生状況をコメント欄に記載すれば回答いたします。

Shopify アプリの構築

Shopify アプリの作成

Shopify アプリ CLIのインストールが完了したら、いよいよShopifyアプリの作成をしましょう。

今回はホームディレクトリ直下に「shopify_dev」フォルダを作成し、その中にアプリを作成します。(お好きな場所に作成して構いません)

$ cd ~
$ mkdir shopify_dev
$ cd shopify_dev

Shopify アプリは、「Node.js」または「Ruby on Rails」で開発できます。

今回は、筆者の専門である「Ruby on Rails」を選択します。

$ shopify create を入力すると、色々質問されるので必要な項目に回答していきます。

$ shopify create
 ? What type of project would you like to create? (Choose with ↑ ↓ ⏎, filter with 'f')
  1. Node.js App
> 2. Ruby on Rails App
(Ruby on Railsで開発するので、「2. Ruby on Rails App」を選択してください)
 ? App name                                                                                                                                                                                                                                         
> (アプリ名を決めてください。)
 ? What type of app are you building? (Choose with ↑ ↓ ⏎, filter with 'f')
  1. Public: An app built for a wide merchant audience.
> 2. Custom: An app custom built for a single client.
(「Public」はアプリストアに公開するためのアプリです。「Custom」は特定のクライアント様に提供するアプリです。テスト用のアプリ開発は「Custom」を使います。)
 i Authentication required. Login to the URL below with your Shopify Partners account credentials to continue.                                                                                                                                      
 Please open this URL in your browser:                                                                                                                                                                                                              
 https://accounts.shopify.com/oauth/authorize?client_id=xxxxxxxx
(表示されたURLをクリック、またはコピーしてブラウザを開きます)

このメッセージが表示されれば成功です。ターミナルに戻りましょう。

 ? Would you like to select what database type to use now? (SQLite is the default)
If you want to change this in the future, run rails db:system:change --to=[new_db_type]. For more info:
https://gorails.com/episodes/rails-6-db-system-change-command
 (Choose with ↑ ↓ ⏎)
  1. no
> 2. yes
(データベースの種類を変更するか聞かれています。デフォルトはSQLiteです。今回は、Herokuサーバーの利用を想定して、PostgreSQLへ変更するため「2. yes」を選択します。)
 ? What database type would you like to use? Please ensure the database is installed. (Choose with ↑ ↓ ⏎, filter with 'f', enter option with 'e')
  1.  SQLite (default)
  2.  MySQL
> 3.  PostgreSQL
  4.  Oracle
  5.  FrontBase
  6.  IBM_DB
  7.  SQL Server
  8.  JDBC MySQL
  9.  JDBC SQlite
  10. JDBC PostgreSQL
  11. JDBC
(「3. PostgreSQL」を選択します)

あなたが決めたアプリ名のディレクトリが作成されました。

以上で、Shopify アプリの環境構築が完了しました。

パートナーダッシュボードへログインし、「アプリ管理」>「すべてのアプリ」を確認してください。

あなたの作成したアプリが登録されています。

アプリの動作確認

作成したアプリの動作確認をします。

動作確認は「ngrok」というツールが必要なので先にセットアップしましょう。

ngrokは、localhost(自分のPC)で動いているサーバーを外部からアクセスできるようにするツールです。

ngrok - Online in One Line
ngrok is the fastest way to put anything on the internet with a single command.

にアクセスしてngrok会員登録を済ませましょう。

次にngrokをダウンロードし、ホームディレクトリで展開してください。

macOS Big Surでは、ngrokアプリの起動が開発元を確認できないため失敗してしまいます。

一度、アプリを直接「Ctrl + ダブルクリック」で開いて承認してください。

その後に、下記のコマンドを入力してください。

$ ~/ngrok authtoken xxxxxx
(ngrokをホームディレクトリで展開していない場合は、ngrokアプリのあるディレクトリを指定してください)

Ruby on Rails, React を構築

次に、Ruby on Railsに必要なツールをインストールしましょう。

まずはGemfileを編集します。

#~/shopify_app/アプリ名/Gemfile
~~省略~~
#ファイル一番下に追加

gem 'dotenv-rails'
gem 'graphql'
gem 'graphiql-rails', group: :development
gem 'react-rails'

次のコマンドを実行して、gemをインストールし、Rails, Reactを構築して行きます。

RailsやReactの構築については説明を省略します。

$ cd ~/shopify_dev/アプリ名
(アプリ名はあなたが作成したアプリ名に書き換えてください)
$ bundle install --path=vendor/bundle
$ bundle exec rails webpacker:install
$ bundle exec rails webpacker:install:react
$ bundle exec rails g react:install
$ bundle exec rails g graphql:install
$ bundle exec rails g shopify_app --with-session-token
(conflictが発生し、Orverwite(上書き)して良いか聞かれた場合、「y + Enter」を押してください)
$ yarn add @apollo/client graphql \
     @shopify/app-bridge-utils@1.26.0 @shopify/polaris \
     react-router react-router-dom
$ bundle exec rails g react:component App
$ bundle exec rails g shopify_app
このコマンドは次の内容を生成します。
  • AuthenticatedControllerセッショントークンを保護リソースに使用
  • ProductsControllerから継承するデフォルトAuthenticatedController
  • HomeControllerセッショントークンベースの認証を使用して製品をフェッチするビューを持つProductsController

この--with-session-tokenフラグは、アプリがセッショントークンベースの認証をすぐに使用することを保証します。

次にビューファイルを作成して行きます。

  • スプラッシュページの作成
  • セッショントークンの保存と認証
  • プロダクトページの作成(認証されたリソースのリクエスト)
スプラッシュページの作成

スプラッシュページは、アプリがセッショントークンのフェッチを開始したことを示すために使用されます。

アプリにトークンがある場合、ユーザーをメインビューに移動する必要があります。

メインビューには、保護または認証されたリソースが含まれている場合があります。

まずは、コントローラーとindex.html.erbを作成しましょう。

$ rails generate controller splash_page index

デフォルトルートのルートを変更しましょう。

# アプリ名/config/route.rb
Rails.application.routes.draw do
  root to: 'splash_page#index'
  get '/home', to: 'home#index'
  ~~省略~~
end
(「root :to => 'home#index」と「get 'splash_page/index'」を削除して上記を記載)

スプラッシュページの読み込みステータスを記載しましょう。

# アプリ名/app/views/splash_page/index.html.erb
<p>読み込み中...</p>

スプラッシュコントローラーがShopifyの組み込みアプリとして動作させ、ショップ情報も取得できるように更新しましょう。

# アプリ名/app/controllers/splash_page_controller.rb
class SplashPageController < ApplicationController
  include ShopifyApp::EmbeddedApp
  include ShopifyApp::RequireKnownShop

  def index
    @shop_origin = current_shopify_domain
  end
end

今後作成するコントローラーが認証を継承できるよう、authenticated_controller.rb にインクルードさせておきましょう。

# アプリ名/app/controllers/authenticated_controller.rb
# frozen_string_literal: true

class AuthenticatedController < ApplicationController
  include ShopifyApp::Authenticated
  before_action :shop_origin

  def shop_origin
    @shop_origin = current_shopify_domain
  end
end

最初のページを表示させるホームコントローラーにAuthenticatedControllerを継承させましょう。

# アプリ名/app/controllers/home_controller.rb

# frozen_string_literal: true

class HomeController < AuthenticatedController
  def index
  end
end

ホームコントローラーのインデックスビューに表示させるコードを上書きしましょう。

# アプリ名/app/views/home/index.html.erb
<%= react_component("App") %>
<%= link_to 'Products', products_path %>
セッショントークンの保存と認証

load_pathを設定して、スプラッシュページからのリダイレクト先を設定しましょう。

# アプリ名/app/views/layouts/embedded_app.html.erb

~~省略~~
<%= content_tag(:div, nil, id: 'shopify-app-init', data: {
      api_key: ShopifyApp.configuration.api_key,
      shop_origin: @shop_origin || (@current_shopify_session.domain if @current_shopify_session),
      load_path: params[:return_to] || home_path,
      debug: Rails.env.development?
    } ) %>
~~省略~~
(「load_path: params[:return_to] || home_path,」を追加しています)
  • ShopifyApp.configuration.api_key とは、このカスタムアプリのAPIキーを取得するメソッドです。
  • @shop_origin インスタンス変数は、Authenticatedクラスで設定されています。
  • @current_shopify_session は include ShopifyApp::RequireKnownShop から取得されています。
  • Rails.env.development? は開発環境であれば true それ以外であれば falseを返すメソッドです。

ここで設定した情報を元にセッショントークンの認証を行っていきます。

最初に読み込まれるファイルの一つにshopify_app.jsがあります。このJavaScriptファイルを使用して、アプリのトークンをフェッチして保存していきます。

# アプリ名/app/javascript/shopify_app/shopify_app.js

import { getSessionToken } from "@shopify/app-bridge-utils";
async function retrieveToken(app) {
  window.sessionToken = await getSessionToken(app);
}

function keepRetrievingToken(app) {
  setInterval(() => {
    retrieveToken(app);
  }, 50000);
}
document.addEventListener("turbolinks:request-start", function (event) {
  var xhr = event.data.xhr;
  xhr.setRequestHeader("Authorization", "Bearer " + window.sessionToken);
});
document.addEventListener("load", function (event) {
  document.addEventListener("turbolinks:render", function () {
    $("form, a[data-method=delete]").on("ajax:beforeSend", function (event) {
      const xhr = event.detail[0];
      xhr.setRequestHeader("Authorization", "Bearer " + window.sessionToken);
    });
  });
});
document.addEventListener("DOMContentLoaded", async () => {
  var data = document.getElementById("shopify-app-init").dataset;
  var AppBridge = window["app-bridge"];
  var createApp = AppBridge.default;
  window.app = createApp({
    apiKey: data.apiKey,
    shopOrigin: data.shopOrigin,
  });

  var actions = AppBridge.actions;
  var TitleBar = actions.TitleBar;
  TitleBar.create(app, {
    title: data.page,
  });

  // Wait for a session token before trying to load an authenticated page
  await retrieveToken(app);

  // Redirect to the requested page
  Turbolinks.visit(data.loadPath);

  // Keep retrieving a session token periodically
  keepRetrievingToken(app);
});

これでページの読み込み時と50秒ごとにセッショントークンをフェッチできるようになりました。

プロダクトページの作成(認証されたリソースのリクエスト)

products_controller.rbはすでに存在しています。デフォルトでは、jsonデータを表示するだけですので、少しカスタマイズしていきましょう。

# アプリ名/app/controllers/products_controller.rb

# frozen_string_literal: true

class ProductsController < AuthenticatedController
  def index
    @products = ShopifyAPI::Product.find(:all, params: { limit: 10 })
  end
end
(「render(json: { products: @products })」を削除しています)

次にプロダクトインデックスビューを作成し、コーディングましょう。

$ mkdir app/views/products
$ touch app/views/products/index.html.erb
# アプリ名/app/views/products/index.html.erb

<%= link_to 'Back', home_path(shop: @shop_origin) %>

<h2>Products</h2>

<ul>
  <% @products.each do |product| %>
    <li><%= link_to product.title, "https://#{@current_shopify_session.domain}/admin/products/#{product.id}", target: "_top" %></li>
  <% end %>
</ul>

次に、AppBridgeを使用してリクエスト認証を行います。

App Bridgeとは、すべてのShopifyプラットフォームで使える埋め込みアプリのための統合開発ツール(SDK)です。開発者は、マーチャントがadminで操作するあらゆる場所にアプリを埋め込むことができるようになります。

Polarisがマーチャント向けUI部品の集まりであるとしたら、App Bridgeは、マーチャント向け機能(動作)部品の集まりであるといえます。

まず、GraphqlControllerAuthenticatedControllerを継承させます。

#アプリ名/app/controllers/graphql_controller.rb
class GraphqlController < AuthenticatedController
~~省略~~
(GraphqlControllerでは認証されたリソースが生成されます。このエンドポイントへのアクセスはセッショントークンが必要です)

次に、test_fieldメソッドに接続に成功というメッセージを追加します。

#アプリ名/app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :test_field, String, null: false,
                               description: 'An example field added by the generator'
    def test_field
      'おめでとうございます!カスタムアプリの環境構築が完了しました。
      素敵な開発ライフを応援しています😆'
    end
  end
end

次に、TestDataというコンポーネントを作成します。

アプリ名/app/javascript/components ディレクトリに TestData.js を作成します。

#アプリ名/app/javascript/components/TestData.js
import { gql, useQuery } from '@apollo/client';
import React from 'react';
const TEST_QUERY = gql`query { testField }`;

export default function TestData() {
  const {loading, error, data} = useQuery(TEST_QUERY);
  if (loading) {
    return (
      <div>Loading...</div>
    );
  } else if (error) {
      console.log(error)
    return (
      <div>何か間違っているようです</div>
    );
  } else {
    return (
      <p>{data.testField}</p>
    );
  }
}

最後にapp/javascript/components/App.js@shopify/app-bridge-utilsauthenticatedFetchにインポートし、Apolloクライアントを作成、フェッチします。

#アプリ名/app/javascript/components/App.js
import {
  ApolloClient,
  ApolloProvider,
  HttpLink,
  InMemoryCache,
} from '@apollo/client';
import { AppProvider, EmptyState, Page } from '@shopify/polaris';
import { authenticatedFetch } from '@shopify/app-bridge-utils';

import enTranslations from '@shopify/polaris/locales/en.json';
import React from 'react';
import TestData from './TestData';

export default function App() {
  const client = new ApolloClient({
    link: new HttpLink({
      credentials: 'same-origin',
      fetch: authenticatedFetch(window.app), // created in shopify_app.js
      uri: '/graphql'
    }),
    cache: new InMemoryCache()
  });

  return (
    <AppProvider i18n={enTranslations}>
      <ApolloProvider client={client}>
        <Page>
          <EmptyState>
            Shopify App with Rails and React ⚡️
            <TestData/>
          </EmptyState>
        </Page>
      </ApolloProvider>
    </AppProvider>
  );
}

このauthenticatedFetch()メソッドは次のことを行います。

  • Authorization: Bearer <session token>このApolloクライアント※によって行われたすべてのリクエストにヘッダーを追加します。トークンは、このメソッドの必要に応じて自動的にキャッシュおよび更新されます。
  • AppBridgeインスタンスを引数として取ります。インスタンスは、app/javascript/shopify_app/shopify_app.jsを使用してすべての埋め込みアプリに提供されます。

※Apolloクライアントとは
GraphQL APIをシンプルにクライアント側で操作するためのライブラリです。

次のコマンドでホストを確認します

$ shopify tunnel start
=> ✔︎ ngrok tunnel running at https://yyyyyyyyyyy.ngrok.io, with account

このホスト(yyyyyyyyyyyy.ngrok.io)をアプリに追加します。yyyyyのところは人によって異なりますので、ご自身の環境で確認してください。

次に、Railsアプリのデータベースを構築します。

$ bundle exec rails db:create
$ bundle exec rails db:migrate

いよいよ、shopify アプリを立ち上げてみます。

$ shopify serve
=> tar @ /usr/bin/tar
 ✓ Installing ngrok...
 ✓ ngrok tunnel running at https://0c43f3f6e46b.ngrok.io
 ⭑ This tunnel will timeout in 7 hours 59 minutes
 ⭑ To avoid tunnels that timeout, it is recommended to signup for a free ngrok
account at https://ngrok.com/signup. After you signup, install your
personalized authorization token using shopify tunnel auth <token>.
 ✓ .env saved to project root
 ? Do you want to update your application url? (Choose with ↑ ↓ ⏎)
 > 1. yes
   2. no
(「1. yes」を選択してください)

 ✓ Whitelist URLS updated in Partners Dashboard                       
                                                                                                                                                                              
 ⭑ To install and start using your app, open this URL in your browser:
 https://yyyyyyyyyyy.ngrok.io/login?shop=xx-xxxxxxxxxxxxxxx.myshopify.com

┏━━ Running server... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃ => Booting Puma
┃ => Rails 6.0.3.4 application starting in development 
┃ => Run `rails server --help` for more startup options
┃ Puma starting in single mode...
┃ * Version 4.3.6 (ruby 2.6.3-p62), codename: Mysterious Traveller
┃ * Min threads: 5, max threads: 5
┃ * Environment: development
┃ * Listening on tcp://127.0.0.1:8081
┃ * Listening on tcp://[::1]:8081
┃ Use Ctrl-C to stop

このRunning server.にエラーメッセージが出なければ立ち上げ成功です。

取得したURL(https://yyyyyyyyyyy.ngrok.io/login?shop=xn-xxxxxxxxx.myshopify.com)にアクセスしてアプリのインストールを行いましょう。(⭑ To install and start using your app, open this URL in your browser:の次の行にあるURLです。)

最後に、次の画面が表示されれば完成です!

本番環境へデプロイ

Herokuサーバの準備

実際に運用する場合、アプリをサーバーにデプロイしておく必要があります。

無料で利用できるHerokuサーバを利用しましょう。まずは、Herokuアカウントを作成しましょう。

アカウントはこちらから登録できます。

クラウド・アプリケーション・プラットフォーム | Heroku
Heroku は、アプリケーションの開発から実行、運用までのすべてをクラウドで完結できる PaaS(サービスとしてのプラットフォーム)です。

アカウントの作成ができたら、herokuコマンドを使ってターミナルからログインします。

もし、herokuコマンドをインストールしていない場合は下記のコマンドをターミナルに入力しましょう。

$ brew tap heroku/brew && brew install heroku

インストールが完了したらログインコマンドです。

$ heroku login

ブラウザにログインページが表示されるので、ログインをクリックしてターミナルに戻ります。

Herokuにデプロイために、Procfileを用意します。

# アプリ名/Procfile
web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development}

この状態でデプロイすれば、Shopifyアプリインストール画面が表示されるはずです。

Gitコミットを行い、Herokuアプリの作成からデプロイを行います。

$ git add .
$ git commit -m "heroku first deploy"
$ heroku create
$ git push heroku master
# ブランチがmainの場合は、git push heroku mainになります。
$ heroku run rails db:migrate

次にHerokuサーバにAPIキーなどを設定していきます。コマンドで設定しても良いのですが、個人的にダッシュボードからの設定がわかりやすいかと思います。

「Herokuにログイン→ダッシュボード→作成したアプリ→Settings→Reveal Config vars」から設定しましょう。設定項目は次の3つです。

  • SHOPIFY_API_KEY
  • SHOPIFY_API_SECRET
  • SCOPES

Herokuが作成したURLを直接入力するか、Herokuダッシュボード上の「Open App」からアプリを開くことができます。

ログインページが表示されれば成功です!

自分のショップURLを入力して、アプリをインストールしてみましょう!

カスタムアプリの設定を更新

実は $ shopify create のコマンドでインストールされた設定がShopify上で必要な設定も行っています。

設定URLとホワイトリストのコールバックURLを今回作成したHerokuのURLに更新する必要があります。

Shopify パートナーダッシュボードのアプリ管理から、今回作成したアプリを選択し、「アプリ設定」を開きます。

アプリURLとリダイレクトURLの許可の記載をHerokuのURLへ変更してください。

最後に、インストールしたストアからアプリを開けることを確認すれば完了です!


※補足

Railsでは、画像リンクはハッシュ化されていますので直接ファイル名を記載しても開くことができません。RailsとReactの組み合わせでは下記のように読み込みを行います。

画像の読み込み方法

# アプリ名/config/webpacker.yml
# resolved_pathsを下記のように変更
resolved_paths: ["app/javascript/packs"]

アプリ名/app/javascript/packs ディレクトリに、images フォルダを追加しましょう。

表示画像は、この images フォルダの中に格納します。

画像を読み込みは下記のように行います。

# インポートの例
import MyImage from 'images/ファイル名.png'

# 利用例
const MyComponent = props => <img src={MyImage} />

これで、画像の表示が完了です。

コメント