初めに
rails APIとReactを用いた開発ではまった点に関するメモです。rails APIをHeroku、Reactをfirebaseにデプロイしたのですが、開発段階では起きなかったエラーも発生したのでそれらについてもまとめています。あくまでエラーに関する記事なのでユーザー認証やメール送信の実装などには深く触れません。
プロジェクトの構成は以下のようになっています。
プロジェクトフォルダ
├ backend
└ frontend
rails 側はAPIモードを指定してrails new、React側はcreate-react-appで作成しました。
# こちらでも問題ありませんが、
rails new . --api
# 以下のようにDBをpostgresqlに指定したほうがHerokuにデプロイする際にごちゃごちゃせずに済みます。
rails new . --api -d postgresql
npx create-react-app .
1. CORS(Cross-Origin Resource Sharing)関連のエラー
バックエンドとフロントエンドを分離したアプリを作成するのが初めてのため、こちらにかなりてこずりました。
まず、CORSとは「異なるオリジン(ドメインみたいなもの)間でのリソースへのアクセス」を制御するためのものだと認識しています。今回であればReact側からrailsAPIをたたいてレスポンスを受け取るわけですが、CORSにより異なるオリジンからのアクセスができないようになっているため、API側で許可してあげる必要があります。
rails側でCORSの設定をするために"rack-cors"をインストールしました。
# 追記もしくはコメントアウトを外す
gem "rack-cors"
bundle install
backend\config\initializers\cors.rbは以下のように記述していました。
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
# アクセスを許可するオリジン(フロントのアドレス)を指定
origins "http://localhost:3000", "本番環境でのアドレス"
resource '*',
:headers => :any,
# ユーザー認証関連
:expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
# リソースに対して許可するHTTPリクエストメソッドを指定
:methods => [:get, :post, :options, :delete, :put, :head, :patch],
# Cookieを利用する場合は以下を記述
:credentials => true
end
end
エラー内容
以上のようにアクセスを許可するオリジンを指定しているにもかかわらず、APIにアクセスする際にCORSエラーで弾かれていました。正確なエラーの文面は覚えていませんが、Access-Control-Allow-Originがheaderに含まれていないとか言われていました。
原因
React側の問題でした。axiosを使ってリクエストを送っていたのですが、axios-case-converterを使ってキャメルケースをスネークケースに変換してあげることで解決しました。表記法の違いによりパラメータの受け渡しなどがうまくいっていなかったものと思われます。
import applyCaseMiddleware from 'axios-case-converter'
import axios from 'axios'
const options = {
ignoreHeaders: true,
}
const client = applyCaseMiddleware(
axios.create({
baseURL: 'APIのURL',
}),
options
);
export default client;
こちらのエラーは本質的にはrubyとjsの表記法の違い(キャメルケースかスネークケースか)によるものですが、発生していたエラーはCORSのものだったのでこの見出しにしました。
これに関しては原因がなかなかわからずあちこちいっじたので、全く別の場所に原因があったのかもしれません。
2. 登録確認メールの送信ができない
エラー内容
ユーザー登録の際にGmailを使って確認メールを送り、メール内のURLを踏んでもらうことでユーザーが認証されるようにしていましたがHerokuにAPIをデプロイしてからメールの送信ができなくなってしまいました。
原因
productionファイルでの設定を行っていなかったことと、環境変数の設定をしっかりしていなかったのが原因でした。
backend\config\environments\development.rbにはメール送信に関する設定をしていましたが、backend\config\environments\production.rbへの設定をしていませんでした。
# 以下の設定をしていなかった
config.action_mailer.delivery_method = :smtp
# HerokuにデプロイしたAPIのURL
host = "#{ENV["HEROKU_APPNAME"]}.herokuapp.com"
config.action_mailer.default_url_options = { host: host, protocol: "https" }
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
domain: 'gmail.com',
user_name: ENV['GMAIL_USERNAME'],
password: ENV['GMAIL_PASSWORD'],
authentication: 'plain',
enable_starttls_auto: true
}
メールアドレスやアプリパスワードは直接ファイルに書き込まず、ENV[~~]のように環境変数を利用しましょう。私の場合はHerokuにデプロイしたのでHerokuのHPにて当該アプリを選択し、settings => config vars にて環境変数を設定しました。
また、backend\config\initializers\devise.rb内にもメールアドレスの設定場所があり、こちらも設定しないと確認メールの送信ができませんでした。
config.mailer_sender = ENV["GMAIL_USERNAME"]
ちなみに、production.rb内のpassword: ENV[~~]は自身が設定したパスワードではなく、アプリパスワードというものです。自身のgoogleアカウント管理画面のセキュリティタブで取得できます。具体的な方法は同様の記事があるため割愛しますが、サクッとできるので問題ないと思います。
追記 3. パラメータ受け渡し時のエラー
エラー内容
ユーザーのIDに紐づいたリソースにアクセスするためにパラメータとしてIDを渡していたが動作しなかった。
原因
axios-case-converterをかませていたことを忘れていた。
React側で以下のようにパラメータ名を渡していた場合、
async function getSubs() {
const res = await client.get("subs", {
parasms: {
// キャメルケースでパラメータを渡している
currentUid: Cookies.get("_uid")
}
}
}
axios-case-converterによりスネークケースに変換されているため、rails側では以下のように受け取る必要がある。
def set_subs
# :currentUidではなく, :current_uid
@subs = Sub.where(uid: params[:current_uid])
end
エラーに対処するうえで大切に感じたこと
バックエンドとフロントエンドのどちらでエラーが発生しているのかを把握することです。どちらが原因なのかがわかればエラー箇所が絞れ、結果的に素早く対処できたように感じます。これを判断する手段として
- Heroku CLIをインストールし、heroku logs でログを確認する
- chromeの検証画面からネットワークタブを選択し、ヘッダーやレスポンスの情報を確認する
の2つが役に立ちました。
chrome検証のネットワークタブは今まで使ったことがありませんでしたが、今回のようなAPIとのやり取りがある場合には強力なツールだと感じました。