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を使ってインストールします。
$ brew tap shopify/shopify
$ brew install shopify-cli
他にも、debian(apt)またはRubyGemsでインストールできますのでご紹介します。
どの方法でインストールしても問題ありません。
debian(apt)
事前に、Shopify cliをダウンロードし展開します。下記のURLから最新の「shopify-cli-x.y.z.deb」ファイルをダウンロードします。
ダウンロードしたファイルをターミナルコマンドからインストールします。
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会員登録を済ませましょう。
次にngrokをダウンロードし、ホームディレクトリで展開してください。
その後に、下記のコマンドを入力してください。
$ ~/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
次にビューファイルを作成して行きます。
- スプラッシュページの作成
- セッショントークンの保存と認証
- プロダクトページの作成(認証されたリソースのリクエスト)
スプラッシュページの作成
スプラッシュページは、アプリがセッショントークンのフェッチを開始したことを示すために使用されます。
アプリにトークンがある場合、ユーザーをメインビューに移動する必要があります。
メインビューには、保護または認証されたリソースが含まれている場合があります。
まずは、コントローラーと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,」を追加しています)
最初に読み込まれるファイルの一つに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を使用してリクエスト認証を行います。
まず、GraphqlController
にAuthenticatedController
を継承させます。
#アプリ名/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-utils
をauthenticatedFetch
にインポートし、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>
);
}
次のコマンドでホストを確認します
$ 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コマンドをインストールしていない場合は下記のコマンドをターミナルに入力しましょう。
$ 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} />
これで、画像の表示が完了です。
コメント