28
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Rails APIモード/Vue] devise_token_authを使ってCRUDの実装

Last updated at Posted at 2021-01-10

はじめに

フロントエンドサーバーとバックエンドサーバーを分けて開発してみたいと思ったので
Vue.jsとRails(APIモード)でログイン機能のついたCRUD処理を書いてみました。

環境

Ruby: 2.6.5
Ruby on Rails 6.0.3

セットアップ

Railsプロジェクト作成

rails _6.0.3_ new devise_token_auth_api --api -d postgresql

cd devise_token_auth_api

rails db:create

Gemfile

gem "devise"
gem "devise_token_auth"
gem "rack-cors"

devise_token_auth インストール

bundle install

rails g devise:install

rails g devise_token_auth:install User auth
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:8080'

    resource '*',
      headers: :any,
      expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'],
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
config/initializers/devise_token_auth.rb
DeviseTokenAuth.setup do |config|
  config.change_headers_on_each_request = false
  config.token_lifespan = 2.weeks
  config.token_cost = Rails.env.test? ? 4 : 10
end

Userモデル

app/models/user.rb
class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  include DeviseTokenAuth::Concerns::User
end
rails db:migrate

ルーティング

config/routes.rb
Rails.application.routes.draw do
  namespace :v1 do
    mount_devise_token_auth_for "User", at: "auth"
  end
end

API動作確認

今回はPostmanを使用します。
Devise_token_authの使い方は下記URLを参考にします。
https://devise-token-auth.gitbook.io/devise-token-auth/usage

サインアップ

HTTPメソッド: POST
URL: http://localhost:3000/v1/auth/
Body: email, password
スクリーンショット 2021-01-10 10.59.15.png

サインイン

HTTPメソッド: POST
URL: http://localhost:3000/v1/auth/sign_in
Body: email, password
スクリーンショット 2021-01-10 11.05.43.png

サインアウト

HTTPメソッド: DELETE
URL: http://localhost:3000/v1/auth/sign_in
Body: uid, access-token, client

サインイン処理した際、Headersタブに必要なパラメータを確認することができます。
スクリーンショット 2021-01-10 11.14.54.png

Vueプロジェクト作成

vue create frontend
*Default ([Vue 2] babel, eslint)を選択

cd frontend
yarn add axios

cd ..

起動コマンド

cd frontend
yarn serve

画面作成

frontend/src/App.vue
<template>
  <div id="app">
    <h3>掲示板に投稿する</h3>
    <div v-if="client === ''">
      <div>
        <h1>SignUp</h1>
        <label for="email">email</label>
        <input id="email" type="email" v-model="email" />
        <label for="password">password</label>
        <input id="password" type="password" v-model="password" />
        <button @click="signup">新規登録</button>
      </div>
      <div>
        <h1>SignIn</h1>
        <label for="email">email</label>
        <input id="email" type="email" v-model="email" />
        <label for="password">password</label>
        <input id="password" type="password" v-model="password" />
        <button @click="signin">SignIn</button>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      name: "",
      email: "",
      password: "",
      uid: "",
      access_token: "",
      client: "",
      title: "",
      content: "",
      tasks: [],
      comment: "",
      posts: [],
    };
  },
  methods: {
    signup() {
      axios
        .post("http://localhost:3000/v1/auth", {
          email: this.email,
          password: this.password,
        })
        .then((response) => {
          localStorage.setItem(
            "access-token",
            response.headers["access-token"]
          );
          localStorage.setItem("client", response.headers["client"]);
          localStorage.setItem("uid", response.headers["uid"]);
          this.access_token = response.headers["access-token"];
          this.client = response.headers["client"];
          this.uid = response.headers["uid"];

        });
    },
    signin() {
      console.log(this.email);
      console.log(this.password);
      axios
        .post("http://localhost:3000/v1/auth/sign_in", {
          email: this.email,
          password: this.password,
        })
        .then((response) => {
          console.log(response);
          localStorage.setItem(
            "access-token",
            response.headers["access-token"]
          );
          localStorage.setItem("client", response.headers["client"]);
          localStorage.setItem("uid", response.headers["uid"]);
          this.access_token = response.headers["access-token"];
          this.client = response.headers["client"];
          this.uid = response.headers["uid"];

        });
    },
    signout() {
      console.log(this.uid);
      console.log(this.access_token);
      console.log(this.client);
      axios
        .delete("http://localhost:3000/v1/auth/sign_out", {
          test: { test: "test" },
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response);
          this.access_token = "";
          this.client = "";
          this.uid = "";
          localStorage.removeItem("uid");
          localStorage.removeItem("access-token");
          localStorage.removeItem("client");
        });
    },
  },
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

SignIn画面

スクリーンショット 2021-01-10 11.24.18.png

タスク機能追加

rails g controller v1/tasks
rails g model Task

モデルの追加

db/migrate/YYYYMMDD_create_tasks.rb
class CreateTasks < ActiveRecord::Migration[6.0]
  def change
    create_table :tasks do |t|
      t.string :title
      t.text :content
      t.references :user
      t.timestamps
    end
  end
end
rails db:migrate

ルーティング

config/routes.rb
Rails.application.routes.draw do
  namespace :v1 do
    resources :tasks
    mount_devise_token_auth_for 'User', at: 'auth'
  end
end

コントローラー

app/controllers/v1/tasks_controller.rb
class V1::TasksController < ApplicationController
  
  before_action :set_task, only: [:show]
  before_action :authenticate_v1_user!
  def index
    tasks = Task.where(user_id: @current_v1_user.id)
    render json: tasks
  end

  def create
    user = User.find_by(email: params[:uid])
    task = Task.new(title: params[:title], content: params[:content], user_id: user.id)
    task.save
  end

  def show
    render json: @task
  end

  def update
  end

  def destroy
    task = Task.find(params[:id])
    task.destroy
    render json: true
  end

  private
  def set_task
    @task = Task.find(params[:id])
  end

end
frontend/src/App.vue
<template>
  <div id="app">
    <div v-if="client === ''">
      <div>
        <h1>SignUp</h1>
        <label for="email">email</label>
        <input id="email" type="email" v-model="email" />
        <label for="password">password</label>
        <input id="password" type="password" v-model="password" />
        <button @click="signup">SignUp</button>
      </div>
      <div>
        <h1>SignIn</h1>
        <label for="email">email</label>
        <input id="email" type="email" v-model="email" />
        <label for="password">password</label>
        <input id="password" type="password" v-model="password" />
        <button @click="signin">SignIn</button>
      </div>
    </div>
    <div v-if="client !== ''">
      <div>
        <h1>Task</h1>
        <button @click="signout">SignOut</button>
        <div v-for="task in tasks" :key="task.id">
          Task:{{ task.id }}, {{ task.title }}, {{ task.content }}
          <button @click="find_task(task.id)">task_find</button>
          <button @click="delete_task(task.id)">Delete</button>
        </div>
      </div>
      <div>
        <h3>Task</h3>
        <label for="task">task</label>
        <input id="task" type="text" v-model="title" />
        <label for="content">content</label>
        <input id="content" type="text" v-model="content" />
        <button @click="create_task">Create_Task</button>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      name: "",
      email: "",
      password: "",
      uid: "",
      access_token: "",
      client: "",
      title: "",
      content: "",
      tasks: [],
      comment: "",
      posts: [],
    };
  },
  methods: {
    signup() {
      axios
        .post("http://localhost:3000/v1/auth", {
          email: this.email,
          password: this.password,
        })
        .then((response) => {
          localStorage.setItem(
            "access-token",
            response.headers["access-token"]
          );
          localStorage.setItem("client", response.headers["client"]);
          localStorage.setItem("uid", response.headers["uid"]);
          this.access_token = response.headers["access-token"];
          this.client = response.headers["client"];
          this.uid = response.headers["uid"];

          this.all_tasks();
        });
    },
    signin() {
      console.log(this.email);
      console.log(this.password);
      axios
        .post("http://localhost:3000/v1/auth/sign_in", {
          email: this.email,
          password: this.password,
        })
        .then((response) => {
          console.log(response);
          localStorage.setItem(
            "access-token",
            response.headers["access-token"]
          );
          localStorage.setItem("client", response.headers["client"]);
          localStorage.setItem("uid", response.headers["uid"]);
          this.access_token = response.headers["access-token"];
          this.client = response.headers["client"];
          this.uid = response.headers["uid"];

          this.all_tasks();
        });
    },
    signout() {
      console.log(this.uid);
      console.log(this.access_token);
      console.log(this.client);
      axios
        .delete("http://localhost:3000/v1/auth/sign_out", {
          test: { test: "test" },
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response);
          this.access_token = "";
          this.client = "";
          this.uid = "";
          localStorage.removeItem("uid");
          localStorage.removeItem("access-token");
          localStorage.removeItem("client");
        });
      this.tasks = [];
    },
    all_tasks() {
      axios
        .get("http://localhost:3000/v1/tasks", {
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response.data);
          this.tasks = response.data;
        });
    },
    find_task(task_id) {
      axios
        .get(`http://localhost:3000/v1/tasks/${task_id}`, {
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response);
          this.task = response.data;
        });
    },
    create_task() {
      console.log(this.uid);
      console.log(this.access_token);
      console.log(this.client);
      axios
        .post("http://localhost:3000/v1/tasks", {
          uid: this.uid,
          "access-token": this.access_token,
          client: this.client,
          title: this.title,
          content: this.content,
        })
        .then((response) => {
          console.log(response);
          this.all_tasks();
        });
    },
    delete_task(task_id) {
      axios
        .delete(`http://localhost:3000/v1/tasks/${task_id}`, {
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response);
          this.all_tasks();
        });
    },
  },
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Task画面

スクリーンショット 2021-01-10 11.51.00.png
GitHub
https://github.com/yodev21/devise_token_auth_app

#終わりに
フロントエンドとバックエンドを分けた際のログイン機能がついたCRUDを実装してみて
ハマるポイントが多数あり学ぶきっかけになりました。

まだまだ改善点や試したいことがあるため、今後は下記を中心に学んでいきたいと思います。
・ 複数テーブルをまたぐデータ取得機能
・ Vue Routerの導入
・ Vuexの導入

参考記事

[Rails] devise token auth を使う

devise-token-auth

28
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?