[Vue/Laravel]APIを実装してみる

## はじめに
こんにちは、ゆきおです。
[こちらの記事](https://qiita.com/kazuyuki_11E0/items/2cbd2e14f5108ac3f891)で作った環境でAPIを実行する簡単なサンプルを作ろうと思います。
もともとあるUserモデルを使ってログイン、ログアウトを実装してみます。

## バックエンド
まずSanctumのマイグレーションを行います
```php
sail artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
sail artisan migrate
```
Sanctumの設定ファイルをプロジェクトにコピーし、新たに追加されるpersonal_access_tokens_tableをデータベースに反映させます。

次にデフォルトのUser.phpにSanctumのトレイトを追加します。
```php
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    // 既存のコード
}
```

次に、ルートの設定をします。
Laravel11ではroutes/web.php、11以前はapi.phpに記述します。
```php
<?php

use App\Http\Controllers\AuthController;
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;

Route::post('/api/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/api/user', [UserController::class, 'show']);
    Route::post('/api/logout', [AuthController::class, 'logout']);
});
```

次にControllerを実装します。
認証するためのAuthController、Userの情報を取得して名前を表示するためのUserControllerを作成します。
（とりあえず動かしたいので型とかけっこう適当に書いてます
```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    public function login(Request $request): \Illuminate\Http\JsonResponse
    {
        $credentials = $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);

        if (Auth::attempt($credentials)) {
            $user = User::where('email', $request->email)->first();
            $token = $user->createToken('auth_token')->plainTextToken;
            return response()->json(['token' => $token], 200);
        }

        return response()->json(['message' => 'Invalid credentials'], 401);
    }

    public function logout(Request $request): \Illuminate\Http\JsonResponse
    {
        if ($request->user()->currentAccessToken() instanceof \Laravel\Sanctum\TransientToken) {
            // Transient tokenの場合は削除せずに成功レスポンスを返す
            return response()->json(['message' => 'Logged out successfully'], 200);
        }

        // 永続的なトークンの場合は削除する
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'Logged out successfully'], 200);
    }
}
```

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    public function show(Request $request)
    {
        return response()->json($request->user());
    }
}
```

バックエンドの実装はこんなもんです。
Sanctumを使うことでベアラートークンによる安全な認証をシンプルに実装できます。

## フロントエンド
次に見た目の実装をします。
これは前回の環境構築で作成したApp.vueを使用しています。

```javascript
<template>
    <div>
        <div v-if="!token">
            <input v-model="email" placeholder="Email" />
            <input v-model="password" type="password" placeholder="Password" />
            <button @click="login">Login</button>
        </div>
        <div v-else>
            <p>Welcome, {{ user?.name }}</p>
            <button @click="logout">Logout</button>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue'
import axios from 'axios'

const email = ref('')
const password = ref('')
const token = ref(localStorage.getItem('token'))
const user = ref(null)

const login = async () => {
    try {
        const response = await axios.post('/api/login', { email: email.value, password: password.value })
        token.value = response.data.token
        localStorage.setItem('token', token.value)
        await fetchUser()
    } catch (error) {
        console.error('Login failed', error)
    }
}

const fetchUser = async () => {
    try {
        const response = await axios.get('/api/user', {
            headers: { Authorization: `Bearer ${token.value}` }
        })
        user.value = response.data
    } catch (error) {
        console.error('Failed to fetch user', error)
    }
}

const logout = async () => {
    try {
        await axios.post('/api/logout', {}, {
            headers: { Authorization: `Bearer ${token.value}` }
        })
        token.value = null
        user.value = null
        localStorage.removeItem('token')
    } catch (error) {
        console.error('Logout failed', error)
    }
}

if (token.value) {
    fetchUser()
}
</script>
```

フロント側のエントリーポイントなどは前回の記事でやっていますが以下のようになっています

### resources/js/app.js
```javascript
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
```

### welcome.blade.php
```php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel</title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    <div id="app"></div>
</body>
</html>
```

### vite.config.js
```javascript
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
});
```

最後に、Tinkerを使ってデータベースにユーザーデータを1つ入れておきます
```php
sail artisan tinker

User::create(['name' => 'Test User', 'email' => 'user@example.com', 'password' => bcrypt('password123')]);
```


これでログイン、ログアウトが実装できたかと思いますのでサーバーを立ててテストします。
```
sail npm run dev
```

![スクリーンショット 2024-07-17 112720.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562437/12b698d9-a180-c325-1348-5d9ef3bf1bf5.png)

![スクリーンショット 2024-07-17 112730.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562437/8084cf5a-a2f0-9891-8e63-5f4bb58176ba.png)

こんな感じで認証ができました。

## おわり
今回は以上です。
ここから登録機能を追加したり、ほかにも色んな機能を実装していくと理解がより深まると思います。

例えばログアウト後はテキストボックスを空にするにはどうしたらいいだろう。とか
エラーが出た場合どういう表示をしたらいいだろう。とか

今回はそういったセキュリティやパフォーマンス、コードのリファクタなど全然やっていないサンプルで、ここからいくらでもアレンジや改良ができるような練習台を作成しました。

何かポートフォリオでも作ろうと思っている方や、アプリを改良していく練習がしたい方は参考にしてみてください。

最後までご覧いただきありがとうございました。