Laravel 6.xにおいてAPIを作成し、フロント側はVue.jsでSPA(Single Page Application)を実装します。
作成するアプリケーションの概要
本記事では下記イラストのような、必要最低限の説明で済む機能を持つアプリケーションを作成します。
1,すでに1件登録した様子です。
2,フォームが未入力もしくは11文字以上を入力し追加しようとするとエラーとなります。バリデーションに関してはAPI側で必要最低限(入力必須/10文字まで)を実装し、フロント側での実装はしていません。
3,編集ボタンをクリックすると、4のように編集フォームが表示されます。挙動が不格好ですが、編集ボタンをクリックすることによって、編集フォーム領域の表示非表示のフラグ(true/falase)を切り替えているだけです。
4,「編集する」もしくは「キャンセル」をクリックすると、編集フォームは消えます。
API側の実装
「http://localhost:8000/api/books/〜」のエンドポイントを持つAPIを作成します。本記事では基本的に手順のみを記述していくので、下記のページもご覧下さい。
あらかじめsample01データベースを用意しておいて下さい。本記事ではMySQLを利用します。.envに適当な値を設定します。
.env
1 2 3 4 5 6 |
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=sample01 DB_USERNAME=hoge DB_PASSWORD=pass |
モデルとマイグレーションファイルを作成します。
1 |
$ php artisan make:model Book --migration |
以上でbooksテーブルに対応したモデル(app/Book.php)とマイグレーションファイルが生成されるので、それぞれ下記のように追記します(ハイライト部分)。
app/Book.php
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Book extends Model { protected $fillable = [ 'title', 'author', ]; } |
database/migrations/2020_02_13_020219_create_books_table.php
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 |
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateBooksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('books', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('title'); $table->string('author'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('books'); } } |
マイグレーションを実行します。
1 |
$ php artisan migrate |
–apiを付けてコントローラーファイルを作成します。
1 |
$ php artisan make:controller BookController --api |
api.phpに下記コードを追記します。/api/books/〜というエンドポイントになります。
routes/api.php
1 |
Route::apiResource('/books', 'BookController'); |
設定されているルート情報を確認します。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ php artisan route:list +--------+-----------+------------------+---------------+---------------------------------------------+--------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+-----------+------------------+---------------+---------------------------------------------+--------------+ | | GET|HEAD | / | | Closure | web | | | GET|HEAD | api/books | books.index | App\Http\Controllers\BookController@index | api | | | POST | api/books | books.store | App\Http\Controllers\BookController@store | api | | | GET|HEAD | api/books/{book} | books.show | App\Http\Controllers\BookController@show | api | | | PUT|PATCH | api/books/{book} | books.update | App\Http\Controllers\BookController@update | api | | | DELETE | api/books/{book} | books.destroy | App\Http\Controllers\BookController@destroy | api | | | GET|HEAD | api/user | | Closure | api,auth:api | +--------+-----------+------------------+---------------+---------------------------------------------+--------------+ |
作成したコントローラーファイルにすでに用意されている index() / store() / update() / destroy() のメソッドを利用します。下記のように追記および編集します(ハイライト部分)。
app/Http/Controllers/BookController.php
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 58 59 60 61 62 63 64 65 66 67 68 69 |
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Book; use App\Http\Requests\StoreBook; class BookController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { return Book::all(); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(StoreBook $request) { Book::create($request->all()); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(StoreBook $request, $id) { $update = [ 'title' => $request->title, 'author' => $request->author ]; Book::where('id', $id)->update($update); } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { Book::where('id', $id)->delete(); } } |
バリデーションのためのフォームリクエストクラスを作成し、生成されたファイルを下記のように編集します(ハイライト部分)。
1 |
$ php artisan make:request StoreBook |
app/Http/Requests/StoreBook.php
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 |
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StoreBook extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'title' => 'required|max:10', 'author' => 'required|max:10', ]; } } |
以上でAPI側の実装は終わりです。curlで確認することができます。
新規作成(POST)
1 |
$ curl -X POST http://localhost:8000/api/books -d 'title=hoge&author=hoge' |
すべてのレコードを確認(GET)
1 |
$ curl http://localhost:8000/api/books/ |
idが1のレコードを編集(PUT)
1 |
$ curl -X PUT http://localhost:8000/api/books/1 -d 'title=hoge2&author=hoge2' |
idが1のレコードを削除(DELETE)
1 |
$ curl -X DELETE http://localhost:8000/api/books/1 |
Vue.jsによるフロント側の実装
Laravelのバージョン6.xでVue.jsをはじめて利用する方は下記ページをご覧下さい。
Laravel 6 Vue.jsを利用する [axios][Laravel Mix]
まずlaravel/uiパッケージをインストールします。
1 |
$ composer require laravel/ui |
Vue.jsで構築するためのファイル等を生成します。
1 |
$ php artisan ui vue |
package.jsonの内容に依存したパッケージをインストールし、コンパイルします。
1 |
$ npm install && npm run dev |
Vueコンポーネント(後述)関連のファイルを編集/保存した際には「npm run dev」で再度コンパイルする必要がありますが、watchでそれら編集/保存を監視しコンパイルを自動化することができます。
1 |
$ npm run watch |
サーバーを起動させます。
1 |
$ php artisan serve |
画面を作成します。Laravelのビュー(テンプレート)を土台として作成し、その中にVue.jsのコンポーネントを表示させるイメージです。「http://localhost:8000/book」としての一画面のSPAとなります。
コントローラーを作成し、ルート情報に追記、後述のようにコントローラーを編集します。Laravelの通常の手順ですので説明は省きます。
1 |
$ php artisan make:controller BookManageController |
routes/web.php
1 |
Route::get('/book', 'BookManageController@index'); |
app/Http/Controllers/BookManageController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class BookManageController extends Controller { public function index() { return view('book.index'); } } |
ビュー(テンプレート)を作成します。bookディレクトリおよびindex.blade.phpをを作成し下記のように記述します。
resources/views/book/index.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="UTF-8"> <title>SPAサンプル</title> <meta name="csrf-token" content="{{ csrf_token() }}"> </head> <body> <div id="app"> <book-component></book-component> </div> <script src="{{ mix('js/app.js') }}"></script> </body> </html> |
13行目
book-componentという名前のVueコンポーネントを表示させます。
app.jsに下記コードを追記して下さい。
resources/js/app.js
1 |
Vue.component('book-component', require('./components/BookComponent.vue').default); |
book-componentという名前のコンポーネントに対して下記で作成するBookComponent.vueを割り当てています。
BookComponent.vueを作成し下記のように記述します。
resources/js/components/BookComponent.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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
<template> <div> <!-- 新規追加フォーム --> <div> <p>タイトル:<input type="text" v-model="title" /></p> <p>著者:<input type="text" v-model="author" /></p> <button @click="addBook">追加</button> </div> <!-- book一覧 --> <div> <ul> <li v-for="book in books" :key="book.id"> {{ book.id }}/{{ book.title }}/{{ book.author }} <button :disabled="isPush" @click="displayUpdate(book.id, book.title, book.author)" > 編集 </button> <button :disabled="isPush" @click="deleteBook(book.id)"> 削除 </button> </li> </ul> </div> <!-- 編集フォーム --> <div v-if="updateForm"> <p>idが{{ updateId }}の編集フォーム</p> <p>タイトル:<input type="text" v-model="updateTitle" /></p> <p>著者:<input type="text" v-model="updateAuthor" /></p> <button @click="updateBook(updateId, updateTitle, updateAuthor)"> 編集する </button> <button @click="updateCancel">キャンセル</button> </div> <!-- エラーメッセージ --> <p v-if="message">{{ message }}</p> </div> </template> <script> export default { data() { return { message: "", isPush: false, updateForm: false, books: {}, title: "", author: "", updateId: "", updateTitle: "", updateAuthor: "" }; }, created: function() { this.getBook(); }, methods: { getBook() { axios .get("/api/books/") .then(response => { this.books = response.data; }) .catch(err => { this.message = err; }); }, addBook() { axios .post("/api/books/", { title: this.title, author: this.author }) .then(response => { this.getBook(); this.title = ""; this.author = ""; this.message = ""; }) .catch(err => { this.message = err; }); }, displayUpdate(id, title, author) { this.isPush = true; this.updateForm = true; this.message = ""; this.updateId = id; this.updateTitle = title; this.updateAuthor = author; }, updateCancel() { this.isPush = false; this.updateForm = false; this.message = ""; }, updateBook(updateId, updateTitle, updateAuthor) { axios .put("/api/books/" + updateId, { title: this.updateTitle, author: this.updateAuthor }) .then(response => { this.getBook(); this.isPush = false; this.updateForm = false; this.message = ""; }) .catch(err => { this.message = err; }); }, deleteBook(id) { axios .delete("/api/books/" + id) .then(response => { this.getBook(); this.message = ""; }) .catch(err => { this.message = err; }); } } }; </script> |
基本的にはaxiosを利用してAPIを叩き、成功したら(.then)一覧画面を更新(getBookメソッド)するという基本的な動作で実現しています。
以上で 「〜/book」にアクセスすればSPAとしてのアプリケーションが確認できます。本記事では一つのコンポーネントを用意してCRUD機能を実装しましたが、Vue Routerを利用したSPAについては下記関連ページもご覧下さい。
関連ページ
Laravel Vue.jsのVue RouterでSPA構築