今回はRailsをAPIモードにしてフロントエンドとバックエンドを分ける方法についてまとめます。
Railsの環境構築については下記記事を参照してください。
今回はAPIモードで環境構築された状態から進めていきたいと思います。
また、今回はフロントはVue.jsで進めます。 Vue.js以外の他のフロントエンド環境でも同じように進められると思いますので参考にしてください(Railsを変えることはないはず)
処理の大まかな流れは以下の流れです
フロントからバックエンドへ処理を依頼→バックエンドで処理を行う→結果をJson形式でフロントエンドに渡す
ここで処理を依頼したり、結果を受け取る際にaxios
というライブラリを使用します。
axiosについて
axiosとは、簡単にHTTPリクエストのやり取りができるPromiseベースのライブラリです。 非同期にHTTPメソッドを取り扱うことができます。 今回は、バックエンドとの非同期な通信に利用します。
まずはリクエストを行うフロントエンドでの処理についてです。
使い方は公式がわかりやすく記載されています。
下記のように使用します(一部分を抜粋しています)。
<script> import axios from 'axios' export default { methods: { async getUser() { try { const res = await axios.get(import.meta.env.VITE_APP_API_BASE + '/users') console.log({ res }); } catch (error) { console.log({ error }) } } } } </script>
やっていることは次のような処理です。
- import fromでaxiosモジュールをインポートする
- 取得したデータを格納するための変数を定義する
- axiosの処理を記述する
axiosの処理とは今回の例のようにaxios.get("URL")でAPIの処理をバックエンドに依頼することです。
今回の例ではgetでしたが、postやputなども可能です。また、パラメータを付与するなども可能です。
そして、実行結果はres
で受け取れるようになっています。
さらに場合に応じてcatch
でエラー処理を行なっています。
バックエンドの処理
次にバックエンドで処理を行い、フロントエンドに結果を渡すまでの流れです。
ここからはRailsで開発する際の基本的な流れと変わりはないです。
Modelの作成
まずはModelを作成します。今回はUserというModelを作成します。
rails g model User id:integer name:string
今回はUserデータをフロントエンドに渡します。 そして、マイグレートを行います。
rails db:migrate
ここでデータは入って入る状態とします。 必要に応じて仮データを入れるなどしてください。
Controllerの作成
次にコントローラーを作成します。
rails g controller Users
ルーティング
ここでルーティングを行います。
ルーティングはフロントエンドでAPIの処理を依頼したときに指定したURLと一致する必要があります。
config/routes.rb
でルーティングの設定を行います。
Rails.application.routes.draw do resources :users end
rake routes
コマンドで確認してルーティングが設定できているか確認しましょう。
Controllerで処理を行う
最後に先ほど作成したコントローラーで処理を行います。
取得したデータはrender json: オブジェクト
とすると、JSON形式のデータを返却することができます。
class UsersController < ApplicationController def index users = User.all render json: { status: 200, message: "API Success", data: users } end end
実行すると下記のようなレスポンスになり、フロントエンドで値を受け取れると思います。(dataというレスポンス)
{ "status":"SUCCESS", "message":"Get User Success!", "data":{"id":1,"name":"TEST"} }
クロスオリジンの設定
Railsの処理をここまで記載しましたが、これだけではレスポンスを取得することはできません。
CORS(クロスオリジン)の設定が必要になります。
Webブウラウザから送信されてくるリクエストが正当なものかどうかをチェックするための仕組みが存在しています。同一生成元ポリシー(Same-Origin Policy)
です。
同一生成元ポリシーを使うことで、異なるオリジンからのリソースへのアクセスに制限をかけることができます。
結果的に、クロスサイトリクエストフォージェリ(CSRF)などのセキュリティリスクを防ぐことができます。
CORSはこの制限を一部解除し、異なるオリジン間でリソースを共有できるようにするための仕組みです。
今回はフロントエンドとバックエンドという異なるオリジンでデータをやりとりするので、CORSの設定が必要になります。(ポート番号が異なるため同じオリジンではない扱いになる)
Rails APIは、rack-cors
というGemを使用することでCORSの設定ができます。
今回は、Gemを導入した状態から進めます。
config/application.rb
で以下の設定を追加します。
config.middleware.insert_before 0, Rack::Cors do allow do origins 'localhost:3000' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end
origins
は、許可するオリジンを指定します。
resource
は許可するリソースファイルを指定します。*とすることで、すべてのリソースファイルを許可します。
headers
は許可するHTTPヘッダーのこと。anyとすることですべて許可しています。
methods
は許可するHTTPメソッドを指定します。
CORSを用いると、異なるオリジンにCRUDリクエストを送る際、そのリクエストが許可されていることをサーバに一度確認してからでないと、リクエストを送ることができなくなります。 このリクエストのメソッドにoptionsが使用されます。 CORSを有効にするとこのプリフライトリクエストのために、optionsメソッドが必要なので、methods内の:optionsを削除すると動かなくなるので削除しないようにしてください。
注意点
ここでRailsをAPIモードにしてフロントエンドとバックエンドを分ける場合の注意点について少し触れておこうと思います。
APIを実行するとActionController::InvalidAuthenticityToken
やCan't verify CSRF token authenticity
というエラーが発生することがあります。
これはRailsのCSRF対策によるものです。
CSRFとは自分がログインしている(常時ログインなどで)サービスへ、知らぬ間にリクエストを送らされる攻撃です。
通常、常時ログインなどでは、cookieにセッション情報が格納されますが、Railsではリクエストのたびにcookieを自動的に取得します。 cookieはブラウザもしくはサーバに保存されているので、別サイトからリクエストが送られたときにも正しいcookieが使用されてしまうのです。
RailsではCSRF対策を自動生成してくれています。
それがapplication_controllerが作られるときに自動で書き込まれるprotect_from_forgery
メソッドです。
RailsではViewでform_forやform_tagを使うと、自動でCSRF対策用のトークンを埋め込んでくれています。
そして、protect_from_forgeryによって、アクション実行前に正しいトークンが付随されているかをチェックしています。
しかし、今回のようにフロントエンドとのやりとりの場合には自分で対策をしなければいけません。
とりあえずエラーが出ないようにするためであれば、protect_from_forgeryをControllerに設定すれば、チェックを行わないのでエラーが発生しないようにはなります。
ただし、根本的な解決ではないです。
リクエスト時にヘッダにX-CSRF-Token
を追加し、そこにトークンの値を埋めて送るとRailsサーバーはその値を使ってprotect_from_forgeryを実行してくれるので、この方法が望ましいです。
class UsersController < ApplicationController // これは根本的な解決ではない protect_from_forgery def index users = Post.order(created_at: :desc) render json: { status: 200, message: "API Success", data: users } end end