Vue.js + LaravelでシンプルなSPA構築チュートリアル：VueとAPI結合編

# はじめに
Vue.jsとLaravelによるSPA実装のチュートリアル記事です。

本記事は、４本の連載記事の４本目です。

[Vue.js + LaravelでシンプルなSPA構築チュートリアル：概要編](https://qiita.com/minato-naka/items/2d2def4d66ec88dc3ca2)
[Vue.js + LaravelでシンプルなSPA構築チュートリアル：Vueフロントエンド編](https://qiita.com/minato-naka/items/9241d9c7a7433985056d)
[Vue.js + LaravelでシンプルなSPA構築チュートリアル：LaravelAPI編](https://qiita.com/minato-naka/items/0e709cb0e6628c82c1c5)
[Vue.js + LaravelでシンプルなSPA構築チュートリアル：VueとAPI結合編](https://qiita.com/minato-naka/items/9362ea5af5f823c95b0b)
↑↑今ここ↑↑


# 前回まで
Vue.jsでフロントエンド実装と、
LaravelのAPI実装が完了しました。

APIにつないでない状態の
・タスク一覧
・タスク詳細
・タスク登録
・タスク編集
のページと、
・タスク一覧取得API
・タスク詳細取得API
・タスク登録API
・タスク更新API
・タスク削除API
が完成している状態です。

今回はこの静的ページと
APIを繋ぎ込んでいきます。

この全体図の赤色部分になります。

![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297693/12fa0dea-ba94-0401-4331-61f0dca40be7.png)



# axios
今回、フロントページから
AjaxでAPIにリクエストを送信して
データの取得や更新を行います。

Ajax通信を簡単に実装するため、
今回はaxiosというパッケージを利用します。
https://qiita.com/ksh-fthr/items/2daaaf3a15c4c11956e9

特に難しいところはありませんが、
axiosの使い方を簡単に把握しておきましょう。


laravel/uiでベースを構築したので、
自分でインストールや設定作業などしなくても
最初からaxiosが利用できる状態です。


# タスク一覧取得API繋ぎ込み
早速、タスク一覧ページとタスク一覧取得APIを繋ぎ込んでみましょう。


まずは`<script>`に必要なデータ、メソッドを定義します。

```diff:resources/js/components/TaskListComponent.vue

<script>
-    export default {}
+    export default {
+        data: function () {
+            return {
+                tasks: []
+            }
+        },
+        methods: {
+            getTasks() {
+                axios.get('/api/tasks')
+                    .then((res) => {
+                        this.tasks = res.data;
+                    });
+            }
+        },
+        mounted() {
+            this.getTasks();
+        }
+    }

</script>
```

まず `data` には空配列の `tasks` を用意します。

そして、 `methods` にある `getTasks()` メソッドで、
タスク一覧取得APIにリクエストして
そのレスポンスを先ほどの `tasks` の中に入れています。
（このメソッドで先ほど話したaxiosを利用してリクエストしています）


そして、画面描画時にこの `getTasks()` メソッドが実行されるように、
`mounted()` でメソッドを呼び出しています。


これで`<script>`側は完了です。


次に`<templete>`側も修正します。

```diff:resources/js/components/TaskListComponent.vue
- <tr>
-     <th scope="row">1</th>
-     <td>Title1</td>
-     <td>Content1</td>
-     <td>Ichiro</td>
-     <td>
-         <button class="btn btn-primary">Show</button>
-     </td>
-     <td>
-         <button class="btn btn-success">Edit</button>
-     </td>
-     <td>
-         <button class="btn btn-danger">Delete</button>
-     </td>
- </tr>
- <tr>
-     <th scope="row">2</th>
-     <td>Title2</td>
-     <td>Content2</td>
-     <td>Jiro</td>
-     <td>
-         <button class="btn btn-primary">Show</button>
-     </td>
-     <td>
-         <button class="btn btn-success">Edit</button>
-     </td>
-     <td>
-         <button class="btn btn-danger">Delete</button>
-     </td>
- </tr>
- <tr>
-     <th scope="row">3</th>
-     <td>Title3</td>
-     <td>Content3</td>
-     <td>Saburo</td>
-     <td>
-         <button class="btn btn-primary">Show</button>
-     </td>
-     <td>
-         <button class="btn btn-success">Edit</button>
-     </td>
-     <td>
-         <button class="btn btn-danger">Delete</button>
-     </td>
- </tr>


+ <tr v-for="(task, index) in tasks" :key="index">
+     <th scope="row">{{ task.id }}</th>
+     <td>{{ task.title }}</td>
+     <td>{{ task.content }}</td>
+     <td>{{ task.person_in_charge }}</td>
+     <td>
+         <router-link v-bind:to="{name: 'task.show', params: {taskId: task.id }}">
+             <button class="btn btn-primary">Show</button>
+         </router-link>
+     </td>
+     <td>
+         <router-link v-bind:to="{name: 'task.edit', params: {taskId: task.id }}">
+             <button class="btn btn-success">Edit</button>
+         </router-link>
+     </td>
+     <td>
+         <button class="btn btn-danger">Delete</button>
+     </td>
+ </tr>
```

まずはべた書きで表示していた
３行のデータを削除します。

そして、先ほど定義した`tasks`データを`v-for`で表示します。
`<tr v-for="(task, index) in tasks" :key="index">`

ID、Title、Content、Person In Chargeの
各カラムは `{{ task.title }}` のようにデータを動的に表示させます。

```diff
- <td>Title1</td>
+ <td>{{ task.title }}</td>
```

また、「Show」「Edit」ボタンの
リンクURLのパラメータもべた書きしていたので、
ちゃんと動的にidを設定します。

```diff
- <router-link v-bind:to="{name: 'task.show', params: {taskId: 3}}">
+ <router-link v-bind:to="{name: 'task.show', params: {taskId: task.id }}">
```

これで、
APIからデータを取得し
それを`v-for`で画面に一覧表示できるようになりました。

commit：[タスク一覧ページAPI繋ぎ込み](https://github.com/MinatoNaka/VueLaravelSpa/commit/11885ce8f9477060505859a7b249b6ec5d5a1f89)

タスク一覧ページ完成です。


# タスク詳細取得API繋ぎ込み
次に、タスク詳細ページとタスク詳細取得APIを繋ぎ込んでいきます。

まずは`<script>`。

```diff:resources/js/components/TaskShowComponent.vue

<script>
    export default {
        props: {
            taskId: String
        },
+        data: function () {
+            return {
+                task: {}
+            }
+        },
+        methods: {
+            getTask() {
+                axios.get('/api/tasks/' + this.taskId)
+                    .then((res) => {
+                        this.task = res.data;
+                    });
+            }
+        },
+        mounted() {
+            this.getTask();
+        }
    }

</script>
```

※taskId: StringではエラーになるのでNumberにした、という[コメント](https://qiita.com/minato-naka/items/2d2def4d66ec88dc3ca2#comment-49447781ca9314f799e8)もありますので、もしうまく動かなければ参考にしてください

一覧ページと同じように、
`data` に空の`task`を用意。
`methods` の `getTask()` でAPIからタスクデータを取得。
`mounted()` で画面描画時にメソッド呼び出し。
としています。


次に`<templete>`側。

```diff:resources/js/components/TaskShowComponent.vue

<div class="form-group row border-bottom">
    <label for="id" class="col-sm-3 col-form-label">ID</label>
    <input type="text" class="col-sm-9 form-control-plaintext" readonly id="id"
-           v-bind:value="taskId">
+           v-model="task.id">

</div>

<div class="form-group row border-bottom">
    <label for="title" class="col-sm-3 col-form-label">Title</label>
    <input type="text" class="col-sm-9 form-control-plaintext" readonly id="title"
-           value="title title">
+           v-model="task.title">

</div>

<div class="form-group row border-bottom">
    <label for="content" class="col-sm-3 col-form-label">Content</label>
    <input type="text" class="col-sm-9 form-control-plaintext" readonly id="content"
-           value="content content">
+           v-model="task.content">

</div>

<div class="form-group row border-bottom">
    <label for="person-in-charge" class="col-sm-3 col-form-label">Person In Charge</label>
    <input type="text" class="col-sm-9 form-control-plaintext" readonly id="person-in-charge"
-           value="Ichiro">
+           v-model="task.person_in_charge">

</div>
```

各データを`v-model`で表示するようにしました。

これでAPI取得したデータをタスク詳細ページに表示できました。

commit：[タスク詳細ページAPI繋ぎ込み](https://github.com/MinatoNaka/VueLaravelSpa/commit/7c9b62debdeda68d380f5c06491db3922ecf1355)


タスク詳細ページ完成です。


# タスク登録API繋ぎ込み
次に、タスク登録ページとタスク登録APIを繋ぎ込んでいきます。

まずは`<script>`。

```diff:resources/js/components/TaskCreateComponent.vue

<script>
-    export default {}
+    export default {
+        data: function () {
+            return {
+                task: {}
+            }
+        },
+        methods: {
+            submit() {
+                axios.post('/api/tasks', this.task)
+                    .then((res) => {
+                        this.$router.push({name: 'task.list'});
+                    });
+            }
+        }
+    }

</script>
```

空の`task`データを用意するところは先ほどと同じです。

`methods` の `submit()` メソッドで、
`task`データをタスク登録APIにPOST送信する処理を書いています。

また、APIによるデータ登録完了後、
`this.$router.push({name: 'task.list'});` でタスク一覧ページにリダイレクトしています。

　
　
次に`<templete>`側。

```diff:resources/js/components/TaskCreateComponent.vue
- <form>
+ <form v-on:submit.prevent="submit">
    <div class="form-group row">
        <label for="title" class="col-sm-3 col-form-label">Title</label>
-        <input type="text" class="col-sm-9 form-control" id="title">
+        <input type="text" class="col-sm-9 form-control" id="title" v-model="task.title">
    </div>
    <div class="form-group row">
        <label for="content" class="col-sm-3 col-form-label">Content</label>
-        <input type="text" class="col-sm-9 form-control" id="content">
+        <input type="text" class="col-sm-9 form-control" id="content" v-model="task.content">
    </div>
    <div class="form-group row">
        <label for="person-in-charge" class="col-sm-3 col-form-label">Person In Charge</label>
-        <input type="text" class="col-sm-9 form-control" id="person-in-charge">
+        <input type="text" class="col-sm-9 form-control" id="person-in-charge" v-model="task.person_in_charge">
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>

</form>
```


各フォームは`v-model`で`task`データとバインディングすることで、
フォームにデータが入力されたら
`<scripts>`側の`task`データも更新されるようになっています。


そして、
`<form v-on:submit.prevent="submit">`
で、フォーム送信時に先ほど定義した`submit`メソッドを呼び出すようにしています。

これで、入力内容が反映された`task`データを
`submit`メソッドでAPI送信できる状態になっています。


commit：[タスク登録ページAPI繋ぎ込み](https://github.com/MinatoNaka/VueLaravelSpa/commit/ab3d25941d16676cc79f411c50665b7f7e91ece6)

これでタスク登録ページ完成です。


# タスク更新API繋ぎ込み
次に、タスク編集ページとタスク更新APIを繋ぎ込んでいきます。

まずは`<script>`。

```diff:resources/js/components/TaskEditComponent.vue

<script>
    export default {
        props: {
            taskId: String
        },
+        data: function () {
+            return {
+                task: {}
+            }
+        },
+        methods: {
+            getTask() {
+                axios.get('/api/tasks/' + this.taskId)
+                    .then((res) => {
+                        this.task = res.data;
+                    });
+            },
+            submit() {
+                axios.put('/api/tasks/' + this.taskId, this.task)
+                    .then((res) => {
+                        this.$router.push({name: 'task.list'})
+                    });
+            }
+        },
+        mounted() {
+            this.getTask();
+        }
    }

</script>
```

タスク詳細ページとタスク登録ページでやったことを
両方やっているだけです。

空の`task`データを用意し、
`getTask()` メソッドでAPIから取得したデータをセットする。

`submit` メソッドでは、
タスク更新APIにputリクエストを送信しています。

　
　
次に`<template>`。

```diff:resources/js/components/TaskEditComponent.vue
- <form>
+ <form v-on:submit.prevent="submit">
    <div class="form-group row">
        <label for="id" class="col-sm-3 col-form-label">ID</label>
-        <input type="text" class="col-sm-9 form-control-plaintext" readonly id="id" v-bind:value="taskId">
+        <input type="text" class="col-sm-9 form-control-plaintext" readonly id="id" v-model="task.id">
    </div>
    <div class="form-group row">
        <label for="title" class="col-sm-3 col-form-label">Title</label>
-        <input type="text" class="col-sm-9 form-control" id="title">
+        <input type="text" class="col-sm-9 form-control" id="title" v-model="task.title">
    </div>
    <div class="form-group row">
        <label for="content" class="col-sm-3 col-form-label">Content</label>
-        <input type="text" class="col-sm-9 form-control" id="content">
+        <input type="text" class="col-sm-9 form-control" id="content" v-model="task.content">
    </div>
    <div class="form-group row">
        <label for="person-in-charge" class="col-sm-3 col-form-label">Person In Charge</label>
-        <input type="text" class="col-sm-9 form-control" id="person-in-charge">
+        <input type="text" class="col-sm-9 form-control" id="person-in-charge" v-model="task.person_in_charge">
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>

</form>
```

これはタスク登録ページと同じです。

各フォームは`v-model`で`task`データとバインディングして、
formの `v-on:submit.prevent="submit"` で`sumit`メソッドを呼んでいます。


commit：[タスク編集ページAPI繋ぎ込み](https://github.com/MinatoNaka/VueLaravelSpa/commit/b80901007baeefb078f1b64c5c725f77bb3e1331)

これでタスク編集ページは完成。


# タスク削除API繋ぎ込み
最後に、タスク一覧ページのDeleteボタンとタスク削除APIを繋ぎ込んでいきます。

まずは`<script>`。

```diff:resources/js/components/TaskListComponent.vue
methods: {
    getTasks() {
        axios.get('/api/tasks')
            .then((res) => {
                this.tasks = res.data;
            });
    },
+    deleteTask(id) {
+        axios.delete('/api/tasks/' + id)
+            .then((res) => {
+                this.getTasks();
+            });
+    }
},
```

`deleteTask()` メソッドを追加しました。
タスクIDを引数で受け取り、
タスク削除APIにリクエストを送信しています。

削除完了したら、
`getTasks()` メソッドを呼んで
タスク一覧を再読み込みしています。


次に`<template>`。

```diff:resources/js/components/TaskListComponent.vue

<td>
-    <button class="btn btn-danger">Delete</button>
+    <button class="btn btn-danger" v-on:click="deleteTask(task.id)">Delete</button>

</td>
```

もともと設置していたDeleteボタンに
`v-on:click="deleteTask(task.id)"` を追加しました。

これで、このボタンをクリックしたら `deleteTask()` メソッドが呼ばれます。


commit：[タスク一覧ページ削除API繋ぎ込み](https://github.com/MinatoNaka/VueLaravelSpa/commit/a298f652c321cfa4dc9a647082311e9e01b8503f)

これでタスク一覧ページの削除処理もできたので、
全ページ、全機能が完成しました。


# おわりに
シンプルなCRUD機能のアプリを
Vue.jsのSPAとLaravelのAPIで構築しました。

Vue側もLaravel側もほとんど難しいところもなく、
かなり簡単に書けたと思います。

今回はできるだけ簡単に一通りの機能を作るチュートリアルとしたかったため、
本来実装すべき処理を省いた箇所が多いです。

Vue側では
Ajaxのエラーハンドリングや
API送信前のバリデーションなど
本来は実装すべきです。

Laravel側もバリデーションや
APIの認証処理などがあるといいです。


今回のチュートリアルで
ざっくりと全体イメージをまずはつかんで、
今後上記のような詳細な処理を少しずつ追加していくといいかと思います。
（最後まで見ていただいた方、最初の記事からLGTMしてもらえるとうれしいです）