今までの開発記録はこちらへ
胡蝶蘭を捨てるくらいならワイが欲しいので、サービス開発する編
公式ドキュメントの言う通り、パッケージをインストールされたら、Inertia.jsが導入されて???になった編
マルチログインを作ってみた編
デザインをtailwindcssに丸投げする編
デザイナーに怒られないために、画像をリサイズする編
AWS×リボ払いで破産へGO編
Google MAPに無人島に飛ばされる編
エンジニアはやせ型が好き編
今回やること
- お気に入り機能作成
- vueコンポーネントの導入
- 非同期通信
- larabel sanctumによるAPI認証
今回はバックエンドより、フロントエンドが大変そうですわ・・・
準備
お気に入りボタンはフォームで作るんだけれど、お気に入りを押すたびにページが更新されるのは面倒くさいですよね
この通信を同期通信というんですけれど、相手が受信するまで待たないといけないとで、利便性が落ちます。
一方で、相手からの受信を待たずに処理を進めてしまう通信のことを非同期通信といいます
えっ、わかるように説明しろって?
それでは宅配便の例を出しましょう。
通常、相手に宅配しようと玄関のチャイムを押したら、相手が出てくるのを待ちますよね?
これが同期通信です
一方で、最近は置き配というサービスがありますよね?
配達員は荷物を置けば仕事が終わり、家主を待つ必要がありません。
これが非同期通信です。
というわけで、Ajaxを利用したいといいたいところですが、axiosというJavaScriptのライブラリを使用していきます。
インストール
npm install axios
resources/js/componentsにFavorite.vueファイルを作成し、書いていきます
<template>
<div v-if="favo">
<button @click="postFavo" class="pt-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
/>
</svg>
</button>
</div>
<div v-else>
<button @click="deleteFavo" class="pt-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
fill="yellow"
viewBox="0 0 24 24"
stroke="gold"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
/>
</svg>
</button>
</div>
</template>
<script>
export default {
el: "#favo",
data() {
return {
favo: this.canfavorite,
error: "",
};
},
props: ["productid", "canfavorite"],
methods: {
postFavo() {
.then((response) => {
axios
.post(`/api/favorites/add`, {
productId: this.productid,
withCredentials: true,
})
.then((response) => (this.favo = false))
.catch((response) => console.error(response.message));
},
deleteFavo() {
axios
.post(`/api/favorites/delete`, {
productId: this.productid,
withCredentials: true,
})
.then((response) => (this.favo = true))
.catch((response) => console.error(response.message));
},
},
};
</script>
Bladeファイルからは"productid", "canfavorite"の値を受け取っています。(後で書きます)
お気に入り登録ボタンと削除ボタンをクリックしたときに、それぞれに対応したメゾットが発火します。
ただし、このままでは機能しません
なぜなら、お気に入りはログインしていないと利用できないため、ユーザーを認証しなければならないからです。
なので、それぞれのユーザーがAPI認証機能を使えるために導入するのがLaravel Sanctumです
Laravel 8.x Laravel Sanctum ということで、こちらもインストールしていきましょう。Laravel Sanctum(サンクタム、聖所)は、SPA(シングルページアプリケーション)、モバイルアプリケーション、およびシンプルなトークンベースのAPIに軽い認証システムを提供します。Sanctumを使用すればアプリケーションの各ユーザーは、自分のアカウントに対して複数のAPIトークンを生成できます。これらのトークンには、そのトークンが実行できるアクションを指定するアビリティ/スコープが付与されることもあります
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
これでapiのミドルウェアを使えるようになるはずです。
ルート
ユーザー機能はweb.phpに書いていましたが、今回はAPIを使うので、api.phpに書いていきます。
どっちに書けばいいのかは調べたところ結構意見が分かれていたので、とりあえず書きます。
Route::prefix('favorites')->middleware(['api', 'auth:users'])->group(function () {
Route::post('/add', [FavoriteController::class, 'add'])->name('favorites.add');
Route::post('/delete', [FavoriteController::class, 'delete'])->name('favorites.delete');
});
お気に入り登録機能実装
まずはお気に入りテーブルとリレーションを行います
画像一枚で説明すると構成はこうなります
コントローラーも書いていきます
class FavoriteController extends Controller
{
public function index() //登録したお気に入りを表示する機能
{
$favoriteItem = Favorite::with('product')
->where('user_id', Auth::id())->get();
return view('user.favorites.index', compact('favoriteItem'));
}
public function add(Request $request) //登録機能
{
$product_id = $request->input('productId');
$itemInFavorite = Favorite::where('user_id', Auth::id())
->where('product_id', $product_id)->first();
if (empty($itemInFavorite)) {
Favorite::create([
'user_id' => Auth::id(),
'product_id' => $product_id,
]);
return response()->json([
'message' => 'お気に入り登録に成功しました'
]);
} else {
return response()->json([
'message' => 'お気に入り登録済みです'
], 403);
}
}
public function delete(Request $request) //削除機能
{
$product_id = $request->input('productId');
Favorite::where('product_id', $product_id)
->where('user_id', Auth::id())->delete();
return response()->json([
'message' => 'お気に入り登録を削除しました'
]);
}
}
解説としては、まずはリクエストからproduct_idを取得し、お気に入りテーブルからuser_idとproduct_idを検索しています。
成功すれば、jsonとして、メッセージを返します。
viewファイル
先に書いていきます。商品詳細のviewファイルとして、resources/views/user/trades/show.blade.phpを作成します
<span id="api"> //お気に入りボタン
@if (empty($favorite)) //お気に入りしていない場合
<favorite-component :canFavorite="true" :productId="{{ $productInfo->id }}" />
</favorite-component>
@else //お気に入りしている場合
<favorite-component :canFavorite="false" :productId="{{ $productInfo->id }}" />
</favorite-component>
@endif
</span>
さっきつくったFavorite.vueコンポーネントを挿入しています。
v-bind
をして、vueファイルにcanFavoriteとproductIdの値を送ります。
vueを読み込む
意外に忘れてしまいがちですが、app.jsにコンポーネントをマウントしましょう!
import { createApp } from 'vue'
import FavoriteComponent from './components/Favorite.vue'
createApp({
components:{
FavoriteComponent,
}
}).mount('#api')
それでは再びvueファイルに戻ります。ボタン部分以外は省略
postFavo() {
axios
.get("/sanctum/csrf-cookie", {
withCredentials: true,
})
.then((response) => {
axios
.post(`/api/favorites/add`, {
productId: this.productid,
withCredentials: true,
})
.then((response) => (this.favo = false))
.catch((response) => console.error(response.message));
});
},
.get("/sanctum/csrf-cookie", {withCredentials: true, })
が追加されましたね
お気に入り処理をする前にユーザーが持つAPIトークンを照合させているんです。
やったぜ
終わりに
実はサイト作成でここが一番苦戦しました。
苦戦したところは
- API自体をそもそもよくわかっていなかった(
API?外部からとってくるやつでしょ?) - 半日つかったにもかかわらず、なんか解決してしまった401エラーさん
- 素では機能するけど、なぜかlaravelでは機能しないvueさん