Laravel5.6とVue.js(Laravel Mix)をつかって基本的なシングルページアプリケーションをつくってみました。。のでメモ。
つくるもの
- Webアプリによくある一連の動き「一覧、登録、表示、更新、削除」(CRUD的なやつ)ができるアプリケーション
- ひとつの基本HTMLだけで各画面遷移も実行される、いわゆるシングルページアプリケーション(SPA)として稼働する
- シングルページアプリケーションだけど、URLは画面ごとに振られる
- バリデーションとかエラー処理とか考えずに、ひとまず動くものをつくる
環境
- Laravel5.6
- Homestead (vagrantでLaravel環境が整う便利なやつ)
サーバサイド
Laravelインストール
インストールしたいディレクトリを作成して、そこに移動後以下のコマンドを実行
$ laravel new
laravel new myapp
とかでディレクトリもつくれるんですが、Homesteadだと最初からディレクトリがある場合のほうが多いと思うのでディレクトリ内で laravel new
をしました。
設定
しばらく待つとインストールが完了してます。
インストールディレクトリ直下の.env
が環境設定ファイルです。
とりあえず、ここのデータベースだけ設定します。
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=DB名
DB_USERNAME=homestead
DB_PASSWORD=secret
APIエンドポイント作成
フロントエンドはVue.jsでつくるので、サーバサイドではエンドポイントだけ作る感じです。
- 記事のテーブル名は topics とする
- 記事のタイトルは title フィールド
- 記事本文は content フィールド
メソッド | URI | 内容 | パラメータ |
---|---|---|---|
GET | /api/topics | 一覧データの取得 | |
GET | /api/topics/[id] | 指定したIDのデータを一つだけ取得 | |
POST | /api/topics | 新規登録 |
title ,content
|
PUT | /api/topics/[id] | 指定したIDのデータを更新 |
title ,content
|
DELETE | /api/topic/[id] | 指定したIDのデータを削除 |
必要なファイルの作成
以下のコマンドで、マイグレーションファイル、モデル、コントローラ、APIリソースファイルの骨組みが作成されます。
$ php artisan make:model Topic -mr
$ php artisan make:resource Topic
マイグレーション
データベーステーブルをPHPから定義するためのファイルです。
ここに構造を記述してコマンド実行すれば、テーブルが出来上がります。
上記のコマンド実行により、database/migrations/
内にファイルが作成されているので、テーブル構造を記述します。
今回は単純にtextカラムのみ設定しましたが、いろいろなカラムタイプや修飾子が使用できます。
public function up()
{
Schema::create('topics', function (Blueprint $table) {
$table->increments('id');
$table->text('title');
$table->text('content');
$table->timestamps();
});
}
参考):
https://readouble.com/laravel/5.6/ja/migrations.html#columns
APIリソース
APIエンドポイントで返される項目を設定します。
LaravelのモデルであるEloquentを元にして、どの項目がAPIとして返されるかを設定します。
Eloquentのプロパティにない項目を作成したり、少し手を加えたりなどもできます。
public function toArray($request){
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'date' => $this->created_at->format('Y-m-d H:i:s'),
];
}
- id,タイトル、内容、作成日付 を返すことにします。
- date項目については、フォーマットを指定しています。Laravelの日付フィールドはCarbonで格納されるので、いろいろといじったりも簡単です。
コントローラー
上記「必要なファイルの作成」の項目で実行したコマンドにより、コントローラーの骨組みも作成されています。
一通りのアクションの骨組みに内容を記述していきます。
//APIリソースを使用
use App\Http\Resources\Topic AS TopicResource;
//~~~ 以下、各メソッドに記述 ~~~~
// 一覧表示
public function index() {
return TopicResource::collection(Topic::all());
}
// 保存
public function store(Request $request) {
$topic = new Topic;
$topic->title = $request->input('title','');
$topic->content = $request->input('content','');
$topic->save();
}
// 1データ表示
public function show(Topic $topic) {
return new TopicResource($topic);
}
// 更新
public function update(Request $request, Topic $topic) {
$topic->title = $request->input('title','');
$topic->content = $request->input('content','');
$topic->save();
}
// 削除
public function destroy(Topic $topic) {
$topic->delete();
}
とりあえず必要なメソッドは上記だけです。
最低限必要なことしか書いてないので、当たり前ですが、非常に短いコードで、APIエンドポイントが実装できます。
ルーティング
コントローラーを書いたらルーティングを記述します。
routes/api.php
に以下を記述するだけです。
Route::resource('topics', 'TopicController');
api.php
は、/api 以下にルーティングするためのファイルで、APIなどのルーティングに使います。
これで、コントローラの各メソッドがURIに割り振られます。
上記の「APIエンドポイント作成」のところで書いたエンドポイントができあがりました。
フロントエンド
Laravel Mix インストール
LaravelにはLaravel Mixというフロントエンド開発環境があります。
webpackを使いやすくラッピングした感じのものですが、とても簡単にVue.jsの開発環境が構築できます。
LaravelのインストールディレクトリにPackage.json
があるのでnpm install
するだけでVue.jsの開発に必要な環境が整います。
Laravelのインストールディレクトリのトップで
$ npm install
Vue Router インストール
続いて、Vue.jsでページ遷移やルーティングを制御するためのライブラリvue-router
をインストールします。
$ npm install --save-dev vue-router
これで最低限必要な環境のインストールは終わりです。
Laravel側のテンプレート設定
今回は、シングルページアプリケーションとして、ページ遷移等も全部Vue.jsで行います。
Laravel側の仕事としては、基本となるHTMLを出力してCSSやJSを読み込むだけ。となります。
なので、viewのテンプレートは一つだけ。
resources/views/
ディレクトリに以下を作成します。
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>サンプルApp</title>
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
<navbar></navbar>
<router-view></router-view>
</div>
<script src="{{mix('js/app.js')}}"></script>
</body>
</html>
そして、ルーティングします。
routes/web.php
の中身を全部消して以下に置き換えます。
そうすることで、先程作成したroutes/api.php
のAPIエンドポイントのURI以外のすべてアクセスは layouts.blade.php
に振られます。
// Route::get('/', function () {
// return view('welcome');
// });
Route::get('/{any}', function() {
return view('layouts');
})->where('any', '.*');
これで、あとはフロント側を書いていくだけです。
Vue.jsとVue Routerの基本設定
まずは、インストールした Vue Router
の基本設定
今回つくるページのURIは以下となります。
URI | 内容 |
---|---|
/ | タイトルを一覧表示 |
/[id] | 指定したIDの内容を表示 |
/create | 新規作成用のフォーム |
Laravel Mixの環境構築用JSファイルに必要な内容を記述します。
require('./bootstrap'); //最初からあります。bootstrap関連の読み込み
window.Vue = require('vue'); //Vue.jsの読み込み
import VueRouter from 'vue-router'; // Vue Routerの読み込み
Vue.use(VueRouter); // Vue.jsで、Vue Routerを使うように設定
// vue-routerによるルーティング設定
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: require('./components/list.vue'), name: 'list' }, // ルートでアクセスしたら、List.vueを表示
{ path: '/create', component: require('./components/Form.vue'), name: 'create' }, // createにアクセスしたらForm.vueを表示
{ path: '/:id', component: require('./components/Detail.vue'), name: 'detail' }, // id番号でアクセスしたらDetail.vueを表示
]
});
// Vueのコンポーネント
Vue.component('navbar', require('./components/Navbar.vue')); //ページ上部にメニューバーを表示させたいので、Navbar.vueを登録
// Vue.jsの実行
const app = new Vue({
router
}).$mount('#app');
各ページ用のファイルを作成
一覧表示のVueファイル作成
<template lang="html">
<div class="container">
<div class="list-group">
<router-link v-for="( item, key, index ) in items" :key="key" :to="{ name: 'detail', params: { id: item.id } }" class="list-group-item">
{{item.title}}
<button class="btn" @click.stop.prevent="onDelete(item.id, key)">削除</button>
</router-link>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
items: null
}
},
mounted: function() {
this.getItems();
},
methods: {
getItems: function() {
axios.get('/api/topics')
.then( (res) => {
this.items = res.data.data;
});
},
onDelete: function(id, key) {
axios.delete('/api/topics/' + id)
.then( () => {
this.$delete(this.items, key);
})
}
}
}
</script>
詳細表示のVueファイル作成
<template lang="html">
<div>
<div class="card" v-if="item">
<div v-if="updated" class="alert alert-primary" role="alert">
更新しました
</div>
<div class="card-body">
<div v-if="!editFlg">
<h1 class="card-title">{{item.title}}</h1>
<div class="card-text">{{item.content}}</div>
</div>
<form v-else>
<div class="form-group">
<input type="text" name="title" id="TopicTitle" class="form-control" v-model="item.title">
</div>
<div class="form-group">
<textarea name="content" id="TopicContent" class="form-control" v-model="item.content"></textarea>
</div>
</form>
</div>
<div class="card-footer">
<time>{{item.date}}</time>
<button class="btn btn-light text-right" v-if="!editFlg" @click="(editFlg = true)">編集</button>
<button class="btn btn-light text-right" v-else @click="onUpdate">更新</button>
</div>
</div>
</div>
</template>
<script>
export default {
data: function( ) {
return {
item: null,
editFlg: false,
updated: false,
}
},
mounted: function() {
this.getItem();
},
methods: {
getItem: function() {
axios.get('/api/topics/' + this.$route.params.id)
.then( ( res ) => {
this.item = res.data.data;
});
},
onUpdate: function() {
axios.put('/api/topics/' + this.item.id, {
title: this.item.title,
content: this.item.content
})
.then( (res) => {
this.editFlg = false;
this.updated = true;
console.log('update')
});
}
}
}
</script>
<style lang="css">
.card-text {
white-space: pre-wrap;
}
</style>
新規作成のVueファイル作成
<template lang="html">
<div class="container">
<div v-if="saved" class="alert alert-primary" role="alert">
保存しました
</div>
<form>
<div class="form-group">
<label for="TopicTitle">タイトル</label>
<input type="text" class="form-control" id="TopicTitle" v-model="title">
</div>
<div class="form-group">
<label for="TopicContent">内容</label>
<textarea class="form-control" id="TopicContent" rows="3" v-model="content"></textarea>
</div>
<button type="submit" class="btn btn-primary" @click.prevent="create">登録</button>
</form>
</div>
</template>
<script>
export default {
data: function() {
return {
saved: false,
title: '',
content: '',
}
},
methods: {
create : function() {
axios.post('/api/topics', {
title: this.title,
content: this.content,
})
.then((res) => {
this.title = '';
this.content = '';
this.saved = true;
console.log('created');
});
}
}
}
</script>
メニューのVueファイル作成
<template lang="html">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<ul class="navbar-nav mr-auto">
<li class="nav-item"><router-link class="nav-link active" :to="{ name: 'list' }">一覧</router-link></li>
<li class="nav-item"><router-link class="nav-link active" :to="{ name: 'create' }">新規作成</router-link></li>
</ul>
</nav>
</template>
JS、CSSファイルの作成
上記のファイルを一式作成したら、以下のコマンドを実行
$ npm run dev
これで各Vueファイルがバンドルされて、public/js/app.js
に書き出されます。
同時に、Laravel Mixに最初からはいっているbootstrapのCSSなども、public/css/app.css
に書き出されます。
今回は、一括で作成してnpm run
をしましたが、実際の作業をおこなうときなどは
$ npm run watch
もしくは
$ npm run watch-poll
を実行することで、ファイル変更を監視して即時にバンドルしてくれるようになります。
以上で、一通り、必要最低限のSPAのウェブアプリが作成されました。
とても簡単に、エンドポイントとフロント側のJSが作成できるので、表から裏までまとめて楽しく開発できます。
参考
https://readouble.com/laravel/5.6/ja/
https://jp.vuejs.org/
https://router.vuejs.org/ja/
https://qiita.com/acro5piano/items/f33381fc60408abe9865