はじめに
本記事はAPIをRailsのAPIモードで開発し、フロント側をVue.js 3で開発して、認証基盤にdevise_token_authを用いてトークンベースの認証機能付きのSPAを作るチュートリアルのVue.js編の記事(その1)になります。
Rails側のチュートリアルを終わらせてからこちらのチュートリアルに取り組まれることを推奨します。
前回: Rails編
次回: Vue.js編(その2)
環境
Vue.js 3.0.5
Vue CLI 4.5.9
npm 6.14.8
node 14.15.0
TypeScript 3.9.7
Vue.jsのボイラープレートを作る
インストール
まずはvue/cliをインストールします。
yarnでもOKですが、今回はnpmでインストールを行います。
既にvue/cliをインストールされている方は、
$ vue --version
でVue CLIのバージョンを確認し、4.5.0よりバージョンが低い場合は、4.5.0以上にアップグレードを行うようにお願いします。
この時、既に作成されているVueアプリの開発に影響が出ることがあるので、
作業ディレクトリ内にのみ最新版のVue CLI を入れるようにしましょう。
初めてVue CLIをインストールする場合
$ npm install -g @vue/cli
4.5.0よりバージョンが低いVue CLIをインストールしたことがある方
$ mkdir 任意のディレクトリ
$ cd 任意のディレクトリ
任意のディレクトリ $ npm install @vue/cli
vue createの実行
無事Vue CLIのインストールに成功したら、vue createコマンドを実行してアプリを作成します。
プロジェクト名はなんでもいいですが、今回はspa-frontとします。
$ vue create spa-front
初期設定は以下のようにしました。(LintやCSS、パッケージ管理ツールの設定はお好みで)
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Router, CSS Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
? Pick the package manager to use when installing dependencies: NPM
作成終了したら、以下のコマンドを実行しましょう。
$ cd spa-front
$ npm run serve
DONE Compiled successfully in 1831ms 12:34:08
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.11.9:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
Issues checking in progress...
No issues found.
http://localhost:8080にアクセスして、
以下のように表示されればボイラープレートの作成は成功です。
axiosを用いてAPIを叩く準備
今回はPromiseベースのHTTPクライアントであるaxiosを用いてRailsで作ったAPIを叩きます。
https://github.com/axios/axios
インストールおよびコード作成
$ npm i axios
API関連のコードはsrc/apiにまとめます。
コンポーネント内に直接APIを叩くコードを書くと、コンポーネントの責務が増えてしまうため、
別ディレクトリにまとめるようにします。
$ mkdir src/api
$ touch src/api/client.ts
client.tsには以下のコードを記述してください。
import axios from 'axios'
export default axios.create({
baseURL: process.env.VUE_APP_API_BASE
})
process.env.VUE_APP_API_BASEは、環境変数からデータを取得する記述方法です。
今回の場合、 http://localhost:3000 がベースのURLになります。
今回は.envファイルで環境変数を管理します。
参考: https://qiita.com/go6887/items/2e254d31b5a4af42f813
以下のファイルをルートディレクトリに作成します。
$ touch .env.development
そして、.env.developmentのファイルに以下を記述します。
VUE_APP_API_BASE=http://localhost:3000
これで、「process.env.VUE_APP_API_BASE」が開発環境だと http://localhost:3000 を返すようになります。
試しにsrc/views/Home.vueでconsole.logを使って、process.env.VUE_APP_API_BASEの値を確認してみましょう。
<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue' // @ is an alias to /src
export default defineComponent({
name: 'Home',
components: {
HelloWorld
}
})
console.log(process.env.VUE_APP_API_BASE)
</script>
一度control + c でサーバーを停止させて、再度npm run serve します。(.env系のファイルはサーバー起動時に読み込まれるため)
chromeの検証ツールを開いて、consoleのタブを開くと、添付画像のようにURLが表示されているかと思います。
問題なく.env.developmentに設定した環境変数を取得できることが確認できました。
これで、他のファイルで以下のようにES6のimport構文で読み込むと、
import Client from '@/api/client'
読み込んだファイル内で以下のようにAPIをコールできます。
Client.post('/auth_sign_in', {...})
今回はaxiosをラップするように書きましたが、仮にaxiosではなく別の(kyとかjavascript標準のfetch APIとか)APIクライアントを使うことになっても、ラップしておけば修正範囲を最小限に留めることができます。
ログイン画面を作成する
簡単なログイン画面を構築してみます。
Login.vueの作成
src/views/Login.vueを作成します。
$ touch src/views/Login.vue
<template>
<div>
<label for="email">
Email
</label>
<input v-model="email" id="Email" type="text" placeholder="Email">
</div>
<div>
<label for="password">
Password
</label>
<input v-model="password" id="password" type="password" placeholder="******************">
</div>
<button @click="handleLogin()">
Sign In
</button>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
import { login } from '@/api/auth'
export default defineComponent({
name: 'Home',
setup () {
const formData = reactive({
email: '',
password: ''
})
return {
...toRefs(formData),
handleLogin: async () => {
await login(formData.email, formData.password)
.then((res) => {
if (res?.status === 200) {
console.log(res)
} else {
alert('メールアドレスかパスワードが間違っています。')
}
})
.catch(() => {
alert('ログインに失敗しました。')
})
}
}
}
})
</script>
ルーティングの設定
src/router/index.tsに/loginのルーティングを定義します。
import Home from '@/views/Home.vue' // @記法に修正
import Login from '@/views/Login.vue' // 追加
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: Home
},
{ // 追加
path: '/login', // 追加
name: 'Login', // 追加
component: Login // 追加
}, // 追加
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
ログイン・ログアウトAPIを叩く準備
次はログイン・ログアウトのAPIを叩けるようにします。
認証tokenを管理する必要があるのですが、ここではLocalStorageにtokenを保存する実装を採用します。
LocalStorageに認証用のtokenを保存することの危険性については各所で指摘されていますが、
管理するデータの機微性に応じて、保存場所を決めるべきかと思います。
LocalStorageにTokenを保存する
まずは認証tokenをLocalStorageに保存する実装を行います。
$ mkdir src/utils
$ mkdir src/types
$ touch src/utils/auth-data.ts
$ touch src/types/auth.ts
// src/utils/auth-data.ts
import { AuthHeaders } from '@/types/auth'
export const getAuthDataFromStorage = (): AuthHeaders => {
return {
'access-token': localStorage.getItem('access-token'),
'client': localStorage.getItem('client'),
'expiry': localStorage.getItem('expiry'),
'uid': localStorage.getItem('uid'),
'Content-Type': 'application/json'
}
}
export const setAuthDataFromResponse = (authData: AuthHeaders): void => {
if (authData['access-token'] && authData['client'] && authData['uid'] && authData['expiry']) {
localStorage.setItem('access-token', authData['access-token'])
localStorage.setItem('client', authData['client'])
localStorage.setItem('uid', authData['uid'])
localStorage.setItem('expiry', authData['expiry'])
}
}
export const removeAuthDataFromStorage = (): void => {
localStorage.removeItem('access-token')
localStorage.removeItem('client')
localStorage.removeItem('uid')
localStorage.removeItem('expiry')
}
// src/types/auth.ts
export type AuthHeaders = {
'access-token': string | null;
'uid': string | null;
'client': string | null;
'expiry': string | null;
'Content-Type': string;
}
上記の関数は
「LocalStorageからtokenを取得してオブジェクトとして返す」
「引数のtokenをLocalStorageに格納する」
「LocalStorageからtokenを削除する」
を行っています。この各種関数をsrc/api/auth.tsで呼び出すようにします。
ログイン・ログアウトのAPIを叩く関数を実装
src/api/auth.tsファイルを作成し、以下のコードを記述します。
$ touch src/api/auth.ts
// src/api/auth.ts
import Client from '@/api/client'
import { User } from '@/types/user'
import {
getAuthDataFromStorage,
removeAuthDataFromStorage,
setAuthDataFromResponse
} from '@/utils/auth-data'
import { AxiosResponse, AxiosError } from 'axios'
export const login = async (email: string, password: string) => {
return await Client.post<User>('/auth/sign_in', { email, password })
.then((res: AxiosResponse<User>) => {
setAuthDataFromResponse(res.headers)
return res
})
.catch((err: AxiosError) => {
return err.response
})
}
export const logout = async () => {
return await Client.delete('/auth/sign_out', { headers: getAuthDataFromStorage() })
.then(() => {
removeAuthDataFromStorage()
})
}
返却される値はUser型として定義したいので、以下のファイルを作成します。
$ touch src/types/user.ts
// src/types/user.ts
export type User = {
allow_password_change: boolean;
email: string;
id: string;
image: string | null;
nickname: string;
provider: string;
uid: string;
}
この状態で npm run serve を実行した後に http://localhost:8080/login にアクセスし、添付画像のような表示がされていればOKです。
ここまで実装できたら、画面からEmailとPasswordを入力して認証ができるかどうかをテストしてみましょう。
画面からログインの動作を確認
http://localhost:8080/login にアクセスして、検証ツールを開いて、Consoleにタブを合わせた状態でログインをしてみます。
Rails編で作成したユーザーのメールアドレス(test-user+1@example.com)とパスワード(password)を入力し、Sign In ボタンをクリックしてください。
ログインに成功すると、Consoleに添付画像のようなデータが表示されるかと思います。
この値が、login関数の返り値になっています。
ApplicationタブのLocal Storageの中身を見てみましょう。
setAuthDataFromResponse関数の呼び出しが正しく行われて、LocalStorageに認証に必要な情報が格納できています。
画面からログアウトの動作を確認
簡易的なログアウトボタンを実装してみます。
Home.vueを以下のように編集してください。
<template>
<button @click="handleLogout()">Logout</button>
</template>
<script lang='ts'>
import { defineComponent } from 'vue'
import { logout } from '@/api/auth'
import router from '@/router'
export default defineComponent({
name: 'Home',
setup () {
const handleLogout = () => {
logout().then(() => {
router.push('/about')
})
}
return {
handleLogout
}
}
})
</script>
logout関数をimportし、buttonのclickイベントが発生した際にlogout関数を呼び出し、
aboutの画面に遷移するようにしています。
http://localhost:8080/ にアクセスするとログアウトボタンが表示されていると思いますので、ボタンを押してください。
ApplicationタブのLocal Storageからaccess-token等が削除されれば、ログアウト成功です。
おわりに
その2へ続く予定です。
次回は「投稿機能」を実装していきます。