Rails API × Vue.js × Firebase AuthでSPA入門

この記事は個人ブログから移動しました。

はじめに

RailsとVue.jsを組み合わせる手法として,大きく分けて2通り存在するという話を以前しました.

Railsのプロジェクトに簡単なVueを組み込むだけなら以前の記事でできますが,vue-routerを使ってSPAの実装などとなってくるとRailsのAPIを叩いてデータをやり取りするというやり方が一般的になります.

今回は,Railsをバックエンドとして,Vue.js+axiosをフロントエンドとして完全に分けて利用する後者の手法を紹介したいと思います.

それぞれは以下のように利用します.

Ruby on Rails:データやセッションや認証を管理
Vue.js:ビューを形成.RailsのAPIを叩いてデータを送ったり受け取ったりする
このページでは,方法を説明するにあたって次のことを行おうと思います.

1. Vue.jsでUserの会員登録フォームを作成
2. Railsにフォーム情報を送信し,DBとFirebase Authenticationに保存
3. Vue.jsのログイン後の画面で,Firebase Authenticationに保存したidをキーとして,DBからデータを取得し,ユーザー情報を表示する

RailsとVue.jsを組み合わせるメリット

これらを組み合わせると,手軽にSPAのアプリケーションが実装できます.
これによって簡単なCRUD機能とユーザー認証を付けたアプリケーションを作成しました.
データはDBに保存しているので,再ログインしても同じ投稿を持っています.

下に簡単なデモ動画でCRUD操作をしました.

画像1

※ 尚,今回はこのアプリケーションの作り方ではなく,あくまでRails APIを叩いてデータの送信や取得をする方法に着眼します.これだけでも結構長くなってしまうので...

【環境】
ruby:2.6.3
rails:6.0.2
node:8.16.2
yarn:1.19.1
npm:6.4.1

RailsをAPIモードで作成

今回RailsはAPIモードで作成します.以下のようにプロジェクトを作成しましょう.

$ rails new sample_app --api

Railsのapiを叩いて使用するのに必要なgemを追加します.以下部分をコメントインしましょう

// Gemfile
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'  // ここのコメントアウトを外す
$ bundle

これによってcors.rbという設定ファイルが作成されます.
最後の部分のコードをコメントインして,以下のように編集しましょう.

// config/initializers/cors.rbを編集
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:8080'   // この部分を修正
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

このポート番号8080はyarn devをした際に割り当てられたポート番号を設定する必要があります.

簡単なユーザーの認証機能を導入したいので,Userモデルとコントローラを作成します.
モデルには名前とメールの他に,DBからデータを取得する際にキーとなるuidというカラムを用意しておきます.
コントローラはnamespaceを使うのが一般的なので,以下のように作成しましょう.

$ rails g model User name:string email:string uid:string
$ rails g controller v1::users

route.rbはこんな感じ

Rails.application.routes.draw do
  namespace :v1 do
    resources :users, only: [:index, :create]
  end
end

users_controllerはこんな感じにしておきます.
apiモードでRailsを作成しているので,Vueへはjsonを返します.
また,indexアクションではキーとなるuidが等しいユーザーの情報を取得して返します.

// users_controller.rb
class V1::UsersController < ApplicationController
  def index
    users = User.find_by(uid: params[:uid])
    render json: users
  end
  def create
    user = User.new(user_params)
    if user.save
      render json: user, status: :created
    else
      render json: user.errors, status: :unprocessable_entity
    end
  end
  private
  def user_params
    params.require(:user).permit(:name, :email, :uid)
  end
end

ステータスコードについて

Railsは,返すレスポンスのステータスコードを自動で生成してくれますが,ステータスオプションをつけることで,変更させることができます.Railsガイドで説明されています.

それぞれのメソッドに対して,ステータスコードを設定しているこちらの記事が良いと思ったので,参考にしました.

フロントエンドの作成

$ vue init webpack Bookers-front

実行するといくつか質問が出てくるので,こんな感じで答えました.

? Target directory exists. Continue? Yes
? Project name bookers-front
? Project description A Vue.js project
? Author shunto <アドレス>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) yarn

これで,Railsプロジェクトの中にvueで設定するファイルが準備できました.
サーバーをそれぞれ実行する必要がありますが,ターミナル2つ開くのは面倒なので,以前の記事でも紹介した通りforemanを使います.(ここも今回の本質ではないので無視してもOK)

$ gem install foreman

プロジェクト直下にProcfileを作成
今回は,プロジェク内のフォルダの中にvue createしてるので,cdしてからyarn devしなければなりません.そこで,シェルで記述しました.

// Procfile
rails: rails s
webpack: sh -c 'cd ./bookers-front/ && yarn dev'

試しにサーバーを立ち上げてみましょう.ポート指定する場合はpオプションつけましょう.

$ foreman start

画像2

Bootstrap導入(任意)

Bootstrapを導入します.(今回の本質ではないので,無視してもOK)

$ yarn add bootstrap-vue
// main.jsに追記
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)

vue-routerを設定

loginとsignupとuserページくらいは設定しときます.

// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import SignUp from '@/views/SignUp'
import SignIn from '@/views/SignIn'
import User from '@/views/users/User'
Vue.use(Router)
export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/signup',
      name: 'signup',
      component: SignUp
    },
    {
      path: '/signin',
      name: 'signin',
      component: SignIn
    },
    {
      path: '/v1/users',
      name: 'User',
      component: User
    },
  ]
})

ユーザーのSignIn機能追加

ユーザーのサインインにはFirebase Authenticationを利用します.
まず,サインインをメールとパスワードでできるように設定します.

画像3

次に,firebaseをインストールします.

$ yarn add firebase

firebaseの設定ファイルを作ります.src/plugins/firebase.jsというファイルを作成しましょう.以下を記述します.

// firebase.js
import firebase from "firebase"
// Your web app's Firebase configuration
const firebaseConfig = {
 apiKey: process.env.API_KEY,
 authDomain: process.env.AUTH_DOMAIN,
 databaseURL: process.env.DATABASE_URL,
 projectId: process.env.PROJECT_ID,
 storageBucket: process.env.STORAGE_BUCKET,
 messagingSenderId: process.env.MESSAGING_SENDER_ID,
 appId: process.env.APP_ID,
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
export default firebase

これは,以前Railsでも紹介しましたが,環境変数をGitなどにあげないようにする方法です.
今回はVue.jsで利用していますがニュアンスは同じです.

Vue.jsでは, Vue-CLI作成時に環境変数を設定できるファイルができます.
config/dev.env.jsというファイルに設定ができます.以下のように環境変数を設定しましょう.(なければ作成してください)

// config/dev.env.js
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
 NODE_ENV: '"development"',
 API_KEY: '"ここに実際のAPI_KEYを入れましょう"',
 PROJECT_ID: '"****************"',
 AUTH_DOMAIN: '"**********************"',
 DATABASE_URL: '"**********"',
 STORAGE_BUCKET: '"*******************"',
 MESSAGING_SENDER_ID: '"************"',
 APP_ID: '"***********************"',
 API_ENDPOINT: '"http://localhost3000"'
})

ちなみにこの情報は,Firebaseコンソールでプロジェクトを作成したのちに画像部分をクリックしたら載っています.

画像4

開いた画面の図の部分に書いてあります.

画像5

これで設定終了です.
あとは,firebaseを使いたいコンポーネントなどで読み込んであげれば利用できます.
ちなみにGitにあげる場合はdev.env.jsはgitignoreに入れましょう.

// .gitignoreに追記
/bookers-front/config/dev.env.js

次にSignUpのview画面をVue.jsで作成します.
ちなみに,Firebase Authenticationの使い方は公式ドキュメントを参照してください.

// SignUp.vue
<template>
 <div class="signup">
   <h2>Sign up</h2>
   <input type="email" placeholder="Email" v-model="email">
   <input type="password" placeholder="Password" v-model="password">
   <button @click="signUp">Register</button>
   <p>Do you have an account?
     <router-link to="/signin">sign in now!!</router-link>
   </p>
 </div>
</template>

<script>
import firebase from '@/plugins/firebase'
export default {
 name: 'Signup',
 data() {
   return {
     email: '',
     password: '',
     user: null,
   }
 },
 methods: {
   signUp: function() {
     firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
       .then(user => {
         alert("Create account!")
         let newUser = {
           uid: user.user.uid,
           email: this.email
         }
       })
       .catch(error => {
         alert(error.message)
       })
   }
 }
}
</script>

<style scoped>
h1, h2 {
 font-weight: normal;
}
ul {
 list-style-type: none;
 padding: 0;
}
li {
 display: inline-block;
 margin: 0 10px;
}
a {
 color: #42b983 ;
}
.signup {
 margin-top: 20px;
 display: flex;
 flex-flow: column nowrap;
 justify-content: center;
 align-items: center
}
input {
 margin: 10px 0;
 padding: 10px;
}
</style>

画像6

正しくFirebaseと連携できてれば,Authenticationの画面に登録されています.

画像7

ここまででRailsプロジェクトの中でフロント部分はVue.jsを使えるようになり,ユーザー認証はfirebase authenticationが使えるようになりました.
あとは,最後にRails APIを叩いてデータをフロントとやり取りする方法を紹介したいと思います.

データをバックエンドに送る

axiosによって,Rails APIにデータを送ります.
まずはaxiosをインストールしましょう.

$ yarn add axios vue-axios

axiosの設定をplugins/axios.jsの中に記述します.
ここではまず,baseURLの設定をしますが,本番環境にデプロイする際に対応するために以下のような設定をしましょう.

// plugins/axios.js
import axios from "axios";
export default axios.create({
 baseURL: process.env.API_ENDPOINT
})

dev.env.jsにAPIのエンドポイントを設定します.
エンドポイントのポート番号はRailsでいつも使用しているlocalhost:3000にします.

// dev.env.jsに追記
API_ENDPOINT="http://localhost:3000"

SignUp.vueの中でaxiosをimportしてデータを送ってみましょう.
axiosのpostの記述はこんな感じ

axios.post("送信先のpath", postするデータ)

実行時は,Rails側に送信でできているかを確認したいので,foremanではなくターミナルを2つ開いて,yarn devとrails sをそれぞれ実行して確認しましょう.

画像8

SignUpを行ったら,Railsを実行したターミナルでDBの保存が確認できました.

バックエンドからデータを取得する

やり方は送信とほとんど同じですね.データの取得はaxios.getで取得できます.
必要なコンポーネントでaxiosによって,uidをキーにデータを取得します.
試しに,表示用にUser.vueを作成してみます.

// User.vue
<template>
 <div>
   <h3>Current User</h3>
   <p>{{ user }}</p>
 </div>
</template>

<script>
import firebase from '@/plugin/firebase'
import axios from "@/plugin/axios"

export default {
 data() {
   return {
     user: null
   }
 },
 mounted() {
   firebase.auth().onAuthStateChanged(async user => {
     if (user) {
       const res = await axios.get(`/v1/users?uid=${user.uid}`)
       this.user = res.data
     }
   })
 }
}
</script>

画像9

こんな感じで,バックエンドからUserデータを取得することに成功しました!
あとは,普通にVue.jsでフロントを作って,データのやり取りは同じように行えば何でもできそうです!!

[補足] リレーションのあるモデルのデータをとってくる

例えば,1人のuserが複数の投稿(post)ができる場合,あるユーザーの投稿全てを取得したいときありますよね.そんなときにuserと関連のあるモデルの値も含めてjsonでデータを渡せたら楽ですね.それを実現してくれるのが,active_model_serializersというgemです.
Gemfileに追加して設定ができます.

// Gemfile
gem 'active_model_serializers'
$ bundle
$ rails g serializer user
$ rails g serializer post

これによってserializerファイルが作成されるので,そこに設定を書きます.

// app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
 attributes :id, :name, :email
 has_many :books  // userと関連するbooksも返してくれる
end

// app/serializers/post_serializer.rb
class PostSerializer < ActiveModel::Serializer
 attributes :id, :title, :body, :user_id
 belongs_to :user  // bookと関連するuserも返してくれる
end

おわりに

RailsとVueのデータのやり取りができました.
今回はUserを登録して表示するだけのかなり単純な利用をしましたが,今後はもう少し発展させていきます!
結構長くなってしまったので,今回はここまで!次回はnuxtも導入してみようと思います