はじめに
フロントエンドサーバーとバックエンドサーバーを分けて開発してみたいと思ったので
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
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
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モデル
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
include DeviseTokenAuth::Concerns::User
end
rails db:migrate
ルーティング
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
サインイン
HTTPメソッド: POST
URL: http://localhost:3000/v1/auth/sign_in
Body: email
, password
サインアウト
HTTPメソッド: DELETE
URL: http://localhost:3000/v1/auth/sign_in
Body: uid
, access-token
, client
サインイン処理した際、Headersタブに必要なパラメータを確認することができます。
Vueプロジェクト作成
vue create frontend
*Default ([Vue 2] babel, eslint)を選択
cd frontend
yarn add axios
cd ..
起動コマンド
cd frontend
yarn serve
画面作成
<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画面
タスク機能追加
rails g controller v1/tasks
rails g model Task
モデルの追加
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
ルーティング
Rails.application.routes.draw do
namespace :v1 do
resources :tasks
mount_devise_token_auth_for 'User', at: 'auth'
end
end
コントローラー
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
<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画面
GitHub
https://github.com/yodev21/devise_token_auth_app
#終わりに
フロントエンドとバックエンドを分けた際のログイン機能がついたCRUDを実装してみて
ハマるポイントが多数あり学ぶきっかけになりました。
まだまだ改善点や試したいことがあるため、今後は下記を中心に学んでいきたいと思います。
・ 複数テーブルをまたぐデータ取得機能
・ Vue Routerの導入
・ Vuexの導入