Laravel + Vue.js + vue-router + axios.postでSPA作成(記事投稿機能)

Laravel + Vue.js + axiosでSinglePageApplication(記事投稿機能)を作成しました。

LaravelではデフォルトでVue.jsやaxiosが使えるようになっています。
クライアントをVue.js、非同期通信をaxios、APIをLaravelで実装しています。

#[準備]
##■DBマイグレーション
今回は記事投稿機能なので記事を保存できるarticlesテーブルを用意する。
usersテーブルはデフォルトで用意されているものを利用。

```php
//database/migrations/2017_08_18_081137_create_articles_table.php
//省略
public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->references('id')->on('users')->unsigned()->index();
        $table->text('content');
        $table->string('title');
        $table->timestamps();
    });
}
//省略
```

##■データの準備
あらかじめusersテーブルにデータをシーディングしておきます。
まず```factories/ModelFactory.php```にファクトリを定義します。

```php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(App\User::class, function (Faker\Generator $faker) {
    static $password;

    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => $password ?: $password = bcrypt('secret'),
        'remember_token' => str_random(10),
    ];
});
```

次に```seeds/DatabaseSeeder.php```のrunメソッドを編集します。

```php
public function run()
{
     //何件でもいいですが適当に5件作りました。
     factory(App\User::class,5)->create();
}
```

artisanコマンドでシーディング。

```
$ php artisan db:seed
```
これでusersテーブルに5件のデータが反映されました。

##■モデル
一人のUserが複数のArticleを持つ可能性があるため、
UserモデルとArticleモデルが１対多の関係になるようメソッドを定義する。

```php
//App/Article.php
class Article extends Model
{
    public function user()
    {
    	return $this->belongsTo(User::class);
    }
}
```

```php
//App/User.php
public function articles()
{
    return $this->hasMany(Article::class);
}
```
##■Webルートを制限
今回SPAなのでブラウザからどのURLでアクセスされても```app.blade.php```というテンプレートが表示されるように```routes/web.php```にルーティングを定義する。
(APIのルーティングは```routes/api.php```に記述する※これは後ほど出てきます)

```php
//routes/web.php
// 全てのURLでapp.blade.phpを表示
Route::get('/{any}', function () {
    return view('layouts.app');
})->where('any', '.*');
```
最後の行の```where('any','.*')```の部分で、```{any}```に入るルートパラメータのフォーマットを指定している。今回は正規表現で全ての文字列を表しているため、ブラウザからどのURLでアクセスしても```app.blade.php```が表示されるようになる。


##■```app.blade.php```の作成

```html
<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

        <link rel="stylesheet" href="{{ mix('css/app.css') }}"></script>

        <script>
            window.Laravel = {
                csrfToken: "{{ csrf_token() }}"
            };
        </script>
    </head>
    <body>
        <div id="app">
            <div class="container">
                <router-view></router-view>
            </div>
        </div>
    </body>
    <script src="{{ mix('js/app.js') }}"></script>
</html>
```
この```<router-view></router-view>```のところに後ほど定義するコンポーネントが表示されます。
ここの名前は```router-view```でなくてはなりません。


ここまでで準備は完了。
記事投稿機能の実装に入ります。

#[実装]
##■API作成(Laravel)
まずはLaravelで記事を投稿するAPIを作成する。

先述した通り、```routes/api.php```に書きます。
ここでは```/article/{id}```というルートにpostすることで記事を保存する処理を記述します。
この```/article/{id}```にVueコンポーネント側からaxiosで非同期でアクセスすることでページ遷移無しで記事を投稿することが出来るようになります。

```php
Route::group(['middleware' => 'api'],function(){
    // 記事を投稿す処理
	Route::post('/article/{id}',function($id){
        //投稿するユーザーを取得
		$user = App\User::where('id',$id)->first();

        //リクエストデータを元に記事を作成
		$article = new App\Article();
		$article->title = request('title');
		$article->content = request('content');

        //ユーザーに関連づけて保存
		$user->articles()->save($article);

        //テストのためtitile、contentのデータをリターン
		return ['title' => request('title'),'content' => request('content')];
	});

});
```


##■クライアント側作成(Vue.js)

###①vue-routerインストール
LaravelではデフォルトでVue.jsやaxiosが入っており、使えるようになっているので、
後はルーティングに必要なvue-routerをインストールします。

```
npm install vue-router
```
vue-routerの詳細はこちらをご覧下さい。
https://router.vuejs.org/ja/

インストールできたら、vue-routerを使えるようにするために```resources/assets/js/app.js```を編集します。

```javascript
// resources/assets/js/app.js

// vueとvue-routerの定義
import Vue from 'vue'
import VueRouter from 'vue-router'

// bootstrap.jsのrequire
require('./bootstrap');

// vue-routerを使う宣言
Vue.use(VueRouter);

//省略
```
ここで大事なのは、
vue-routerのimportを行うことと、```Vue.use(VueRouter)```でvue-routerを使いますよ、ということを明記してあげることです。


###②app.jsでルーターの定義
次にコンポーネントとルーティングの定義を```app.js```で行います。
ここでは、```/article/create```というルートでアクセスするとCreate.vueコンポーネントを表示することにします。
また、TOP画面も作成するために、```/```でアクセスすると```Index.vue```コンポーネントを表示するようにします。

```javascript
// resources/assets/js/app.js

// vue-routerのインスタンス化、オプションroutesでアクセスされるパスとその時に表示するComponentを指定
const router = new VueRouter({
	mode: 'history',
    routes: [
        // TOPページ
        { path: '/', component: require('./components/Articles/Index.vue') },
        // 記事投稿フォームページ
        { path: '/article/create', component: require('./components/Articles/Create.vue') },
    ]
});
```
ここでmodeをhistoryにしないと、ブラウザで表示した時にURLの最後になぜか#がついてしまうのでhistoryにしておきます。

最後にVueをインスタンス化します。

```javascript
// resources/assets/js/app.js

const app = new Vue({
	router,
    el: '#app'
});
```


###③Vueコンポーネントのテンプレート作成(記事投稿画面)
次にVueコンポーネントを作成して行きます。
まずは、Index.vueです。

```html
<template>
	<div>
        <p>
            <router-link to='/article/create'>記事作成</router-link>
        </p>    
    </div>
</template>
```
TOP画面になるIndex.vueには記事作成画面へのリンクだけ表示するようにします。
この```to=''```の部分で先ほどapp.jsのrouterに定義したCreate.vueを表示するルートを記述します。

次にCreate.vueを作成します。

```html
<template>
	<div>
	    <div>
            <h4>記事投稿</h4>
            <div>
                <!-- v-modelに名前をつけることで入力値をメソッドやテンプレートで使える -->
                <input v-model="title" type="text" name="title" placeholder="タイトル">
                <textarea v-model="content" class="form-control" rows="4" placeholder="コンテンツ"></textarea>

                <!-- 試しにinputタグに入力された値を表示してみます -->
                <p>{{ title }}</p>
                <p>{{ content }}</p>

                <!-- v-onでクリックイベントを定義。投稿ボタンをクリックするとpostArticleメソッドが実行されます -->
                <button v-on:click="postArticle">投稿</button>
            </div>
	    </div>
    </div>
</template>

<script>
    export default {
    	// コンポーネント作成時に実行する処理を定義
        // created() {
        // 今回は特に処理はありません
        // },

        // テンプレート内で使う変数を定義
        data() {
            return {
                title:'',
                content:''
            }
        },
        // メソッドの定義。ここでv-on:click=''で記述したpostArticle()メソッドを定義します
        methods: {
            postArticle(){
                // テンプレートのv-modelのtitleとcontentの入力値を取得しarticleという配列に格納
                var article = {
                    'title': this.title,
                    'content': this.content
                };

                // 今回はuserのidを1とします。
                var id = 1;

                // axios.postの第１引数にルートを、第２引数にポストするデータの配列を渡します
                axios.post('/api/article/' + id,article).then(res => {

                    // テストのため返り値をコンソールに表示
                    console.log(res.data.title);
                    console.log(res.data.content);
                });
            }

        }
    }
</script>
```

##■気をつけるポイント

###①apiを利用する場合のルート
LaravelでAPIを作成する場合、```routes/api.php```にルートを記載しますが、
その場合、APIにアクセスする側は```/api/article/1```のようにルートの先頭にapiをつける必要があるようです。

###②コンポーネント内でのデータの扱い
・data()で初期化する
・v-modelで取得するデータはthis.titleという形出ないとメソッドで扱えない
・v-modelで取得するデータをテンプレートで表示する場合はthisは無くていい

###④Vue.use(VueRouter)を忘れない

##■ざっくり処理の流れ
・記事作成リンククリック(ユーザー)[Index.vue]
↓
・コンポーネント上のテンプレート表示[Create.vue]
↓
・タイトル・コンテンツ入力(ユーザー)[Create.vue]
↓
・投稿ボタンクリック[Create.vue]
↓
・定義しておいたpostArticle()メソッド実行[Create.vue]
↓
・axiosでAPIへアクセス[Create.vue]
↓
・裏側でtitle/contentに入力されていたデータをDBに保存[api.php]
↓
・リクエストデータを返却[api.php]
↓
・リクエストデータを受け取ってコンソールに表示[Create.vue]

以上です。
SPAは色々やってみたいと思っているのでまた何か実装したら記事にしようと思います。