laravelでAPI作って、フロントはvue.js+axios(非同期なajax)で、SPAなCRUDアプリを作ってみた(後編)
API作成とVUEの結合
1, DBは、MySQLではなくSQLite
# SQLiteの下準備は、空ファイルを作成するだけ
touch database/database.sqlite
.envのDB接続設定をmysqlからsqliteにする
| 1 2 3 4 5 6 | DB_CONNECTION=sqlite # DB_HOST=127.0.0.1 # DB_PORT=3306 # DB_DATABASE=homestead # DB_USERNAME=homestead # DB_PASSWORD=secret | 
これだけでDB(sqlite)が使えるようになる!
php artisan migrate
# たまにmysqlのキャッシュが残っている事があるのでクリアしておく
php artisan config:cache
2, モデル・テーブル・コントローラー・シーダーを一気に作成
| 1 2 3 4 5 6 7 | php artisan make:model Task --all Model created successfully. Factory created successfully. Created Migration: 2020_08_21_033554_create_tasks_table Seeder created successfully. Controller created successfully. | 
3, テーブル定義
database/migration/2020_08_21_033554_create_tasks_table.php
| 1 2 3 4 5 6 7 | Schema::create('tasks', function (Blueprint $table) {     $table->id();     $table->string('title', 100);     $table->string('content', 100);     $table->string('person_in_charge', 100);     $table->timestamps(); }); | 
4, Taskモデルは、idカラム以外は自由に修正出来るようにする。
| 1 2 3 4 | class Task extends Model {     protected $guarded = ['id']; } | 
5, シーダーで適当なレコード生成
database/seeds/TaskSeeder.php
| 1 2 3 4 5 6 7 8 9 10 | public function run() {    for ($i = 1; $i <= 10; $i++) {        Task::create([            'title' => 'title' . $i,            'content' => 'content' . $i,            'person_in_charge' => 'person_in_charge' . $i,        ]);    } } | 
database/seeds/DatabaseSeeder.phpで読み込む
| 1 2 3 4 5 | public function run() {     // $this->call(UserSeeder::class);     $this->call(TaskSeeder::class); } | 
6, APIのルーティング
routes/api.php
| 1 2 3 4 | // TaskのAPI Route::group(['middleware' => ['api']], function(){     Route::resource('task', 'TaskController'); }); | 
これで、自動的にRESTful APIのルーティングが完了する。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | php artisan route:list +--------+-----------+----------------------+--------------+---------------------------------------------+------------+ | Domain | Method    | URI                  | Name         | Action                                      | Middleware | +--------+-----------+----------------------+--------------+---------------------------------------------+------------+ |        | GET|HEAD  | api/task             | task.index   | App\Http\Controllers\TaskController@index   | api        | |        | POST      | api/task             | task.store   | App\Http\Controllers\TaskController@store   | api        | |        | GET|HEAD  | api/task/create      | task.create  | App\Http\Controllers\TaskController@create  | api        | |        | GET|HEAD  | api/task/{task}      | task.show    | App\Http\Controllers\TaskController@show    | api        | |        | PUT|PATCH | api/task/{task}      | task.update  | App\Http\Controllers\TaskController@update  | api        | |        | DELETE    | api/task/{task}      | task.destroy | App\Http\Controllers\TaskController@destroy | api        | |        | GET|HEAD  | api/task/{task}/edit | task.edit    | App\Http\Controllers\TaskController@edit    | api        | |        | GET|HEAD  | api/user             |              | Closure                                     | api        | |        |           |                      |              |                                             | auth:api   | |        | GET|HEAD  | {any}                |              | Closure                                     | web        | +--------+-----------+----------------------+--------------+---------------------------------------------+------------+ | 
7, TaskController.phpでAPI処理内容を記述。スゴいシンプル!
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php namespace App\Http\Controllers; use App\Task; use Illuminate\Http\Request; class TaskController extends Controller {     public function index()     {         return Task::all();     }     public function show(Task $task)     {         return $task;     }     public function store(Request $request)     {         return Task::create($request->all());     }     public function update(Request $request, Task $task)     {            $task->update($request->all());         return $task; //更新したデータを返す。     }     public function destroy(Task $task)     {             $task->delete();         return $task; //削除したデータを返す。     } } | 
8, 試しにコマンドラインから、CRUDのAPIを叩いてみる
| 1 2 3 4 5 6 7 8 9 10 11 12 | # 全部GET curl http://localhost:8000/api/task  # 指定IDのレコードだけGET curl http://localhost:8000/api/task/3 # レコード作成&更新(Win10だとうまく行かなかったので、Advanced Rest Clientで確認したら動作した) curl -X POST -H "Content-Type: application/json" http://localhost:8000/api/task -d "{\"title\": \"ppp\",\"content\": \"テスト\",\"person_in_charge\": \"person_in_chargeテスト\"}" curl -X PUT -H 'Content-Type: application/json' http://localhost:8000/api/task/11 -d '{"title": "ttt","content": "Test Message"}' # 削除はWin10でもOK curl -X DELETE http://localhost:8000/api/task/11 | 
9, フロントエンドのvue.js・バックエンドのlaravel apiを、axios(ajax)で結びつける
タスク一覧をAPIで取得
resources/js/components/TaskListComponent.vue
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <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> <script>     export default {            // tasks配列は、最初は空         data: function () {             return {                 tasks: []             }         },         methods: {             // API経由で全タスクを取得             getTasks() {                 axios.get('/api/task')                     .then((res) => {                         this.tasks = res.data;                     });             }         },         //マウントし終わったらAPIでタスク取得する。         mounted() {             this.getTasks();         }     } </script> | 
タスク詳細ページ
resources/js/components/TaskShowComponent.vue
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | v-model="task.title"> のように動的データバインディングを行う。 <script>     export default {         props: {             taskId: String         },         data: function () {             return {                 task: {}             }         },         methods: {             getTask() {                 axios.get('/api/task/' + this.taskId)                     .then((res) => {                         this.task = res.data;                     });             }         },         mounted() {             this.getTask();         }     } </script> | 
ハリボテだったタスク登録画面
resources/js/components/TaskCreateComponent.vue
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <template>     <div class="container">         <div class="row justify-content-center">             <div class="col-sm-6">                 <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" 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" 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" v-model="task.person_in_charge">                     </div>                     <button type="submit" class="btn btn-primary">Submit</button>                 </form>             </div>         </div>     </div> </template> <script>     export default {         data: function () {             return {                 task: {}             }         },         methods: {             submit() {                 axios.post('/api/task', this.task)                     .then((res) => {                         this.$router.push({name: 'task.list'});                     });             }         }     } </script> | 
同じくハリボテだったタスク更新画面
resources/js/components/TaskEditComponent.vue
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <template>     <div class="container">         <div class="row justify-content-center">             <div class="col-sm-6">                 <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-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" 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"  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" v-model="task.person_in_charge">                     </div>                     <button type="submit" class="btn btn-primary">Submit</button>                 </form>             </div>         </div>     </div> </template> <script>     export default {         props: {             taskId: String         },         data: function () {             return {                 task: {}             }         },         methods: {             getTask() {                 axios.get('/api/task/' + this.taskId)                     .then((res) => {                         this.task = res.data;                     });             },             submit() {                 axios.put('/api/task/' + this.taskId, this.task)                     .then((res) => {                         this.$router.push({name: 'task.list'})                     });             }         },         mounted() {             this.getTask();         }     } </script> | 
最後に削除ボタン。画面遷移を伴わないのでスムーズな動作(画面のDOMを削除して、裏でAPI経由でレコード削除)
scriptタグに追加
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | methods: {     // API経由で全タスクを取得     getTasks() {         axios.get('/api/task')             .then((res) => {                 this.tasks = res.data;             });     },     // API経由で指定IDのタスクを削除     deleteTask(id) {         axios.delete('/api/task/' + id)             .then((res) => {                 this.getTasks();             });     } }, |