Nuxt.js+Laravelで開発しているCMSの紹介

みなさんNuxt.jsとLaravelはお好きですか？
僕は大好きです。

まだNuxt.jsもLaravelも初心者ですが、その2つを使って開発しているCMSについての紹介と技術周りでハマった点、使ったものなどを紹介させていただきます。

# 自己紹介
遅れましたが自己紹介を少し。

現在中学３年生でプログラミングは小学4年生からはじめました。
今はLaravelやNuxt.js、ゲーム周りだとUnityやScratchなどを使っています。
最近ポートフォーリオを作ってみたので良ければ見てみてください。

<http://me.hota1024.com/>

では本題に移ります

# CMSとは？
CMSとはContent-Management-Systemの略です。
代表的なものだと

- Wordpress
- OctoberCMS

などが挙げられます。

今回僕が開発しているCMSは以下の機能を持っています。

- 記事投稿
- ファイル管理
- タグ
- SPA

# なぜ作ろうと思ったか
主な理由は以下のとおりです

- 自分で開発したCMSなら記事などの投稿のモチベーションが保持できる
- 管理画面やブログがSPAなCMSがほしかった
- 腕試し

# 実際に開発しているもの
![io-cms-1024のコピー.png](https://qiita-image-store.s3.amazonaws.com/0/154299/e71cb608-6b41-ea8b-a3fa-bcaff9ef76ea.png)

ロゴは適当です。

下の画像が実際の記事編集画面です。
![スクリーンショット 2018-12-04 22.14.04.png](https://qiita-image-store.s3.amazonaws.com/0/154299/8c788b7c-b5df-a771-1f72-72c72496d20e.png)

記事はMarkdownで記述します。
そのMarkdownの機能について少し紹介します。

MarkdownのコードはQiitaみたいにファイル名を指定できるようにしています。
<img src="https://qiita-image-store.s3.amazonaws.com/0/154299/40fec881-5c3b-51d1-4c30-2d779f8f7de4.png" width="512px">
変換後
<img src="https://qiita-image-store.s3.amazonaws.com/0/154299/ed117046-2ca6-f7e3-ebd1-27414b47e467.png" width="512px">

Markdownも少し拡張していてアラートを出せるようにしています。

<img src="https://qiita-image-store.s3.amazonaws.com/0/154299/94d2d7b2-1707-21af-df47-039c7cf2236c.png" width="512px">
変換後
<img src="https://qiita-image-store.s3.amazonaws.com/0/154299/b07b28e0-55dd-8888-ac90-774a217ed805.png" width="512px">

# 技術周りの紹介
今回使ったものは以下のとおりです

- エディタ
    - PhpStorm
    - WebStorm
- バックエンド
    - Laravel
    - Laravel Homestead
- フロントエンド
    - Nuxt.js
    - Vuetify

最近Vuetifyというマテリアルデザインフレームワークを気にっています。
コンポーネントが豊富で機能も優秀すぎる。
おすすめなのでぜひ使ってみてください。

## [Vuetify](https://vuetifyjs.com/ja/getting-started/quick-start)
<img width="995" alt="スクリーンショット 2018-12-04 22.26.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/154299/6deffb76-d06a-bd81-1dba-b815277e4aee.png">

Nuxt.jsは普段僕はVue.jsを使っていたのですが、Nuxt.js知ったときはかなり衝撃的でした。
とにかく楽。コマンド打つだけで静的サイトを生成したりSSRもできたりで便利すぎる。
こちらもおすすめです。

## [Nuxt.js](https://ja.nuxtjs.org/)
<img width="1268" alt="スクリーンショット 2018-12-04 22.29.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/154299/d2c13a4e-2553-be48-c7fe-c3abc95cd19a.png">
そいういえば最近Nuxt.jsのトップページの下の方にすぐにNuxt.jsを試せる[CodeSandbox.io](https://codesandbox.io/)の埋め込みが追加されましたね。
これはこれで嬉しい。

ちなみにNuxt.jsでカウンターアプリやVue.jsで「カップ麺タイマー」などを作ったので良ければ見てみてください。

<http://nuxt-counter.xii.jp/>
<img width="624" alt="スクリーンショット 2018-12-04 22.43.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/154299/3dcd6880-540f-5aef-a3b4-eadaa682d65c.png">

<http://ntimer.xii.jp/#/>
<img width="675" alt="スクリーンショット 2018-12-04 22.44.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/154299/73608a0b-891a-a79b-e307-25ec01c2ac45.png">


## [Laravel](http://laravel.jp/)
<img width="1260" alt="スクリーンショット 2018-12-04 22.33.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/154299/0c432f02-54b0-b047-db3a-4a6db6a71726.png">
最近Ruby on Railsが熱いですがLaravelも再び熱くなってきています。
PHPをやり始めて2年間、そもそも「Webフレームワーク」というものの存在を知らなくて初めてLaravelを知ったときは今までの苦労はなんだったのかと...
それぐらい簡単に色々なことができるPHPフレームワークなのでぜひ使ってみてください。

ちなみに今回io-cmsを開発するにあたって**Laravel Homestead**というVagrantのBoxを使用しました。

やっぱり仮想環境はいいですね。

# つまりポイント
今回このio-cmsの開発にあたって詰まったポイントをいくつか紹介したいと思います。

## 許可
一番悩んだのがどうやってログインしているユーザーだけが管理画面で操作をできるようにするかです。

基本的にNuxt.jsからLaravelのAPIを叩くのでこのときに認証情報をどうやって送ればいいのかぐぐってみるとちょうどいいものが見つかりました。

どうやら**JWT**という方法があるらしいのでそのJWTをLaravelで使うためのライブラリを探してみました。

<https://github.com/tymondesigns/jwt-auth>

このライブラリが一番使いやすそうでした。
いろいろ設定ファイルをいじってNuxt.js側でcookieにトークンを格納するロジックを書くだけで実現できてしまいました。

## axios地獄
ちょっとふざけてるように感じますが、これもかなり悩まされました。

例えば以下のようなコードがあるとします。

```js
const state = {
  methods: {
    async new () {
      this.article = {
        id: -1,
        title: '',
        body: '',
      }
    },
    async get () {
      this.article = await this.$axios.$get('article/' + this.id)
    },
    save () {
      this.$axios.$put('article/' + this.article.id, this.article)
    },
    delete () {
      this.$axios.$delete('article/' + this.id)
    }
  }
}
```
今回Nuxt.jsからLaravelへAjaxするにあたって**axios**というライブラリを用いました。
上のコードだとそのaxiosの呼び出しである**this.$axios**がたくさん見られます。
このコードの問題点は以下のとおりです。

- 一回のリクエストの**コードが長い**
- 他のプログラムでも同じコードを書いたときに **APIの仕様変更をした際に修正する箇所が多い**

とりあえずORMみたいなAPIでリクエストできたら見やすくなるかなーなんて思って以下のようにリクエストできるようにしました。
ただしRESTfulAPI前提です。

```js
const state = {
  methods: {
    new () {
      this.article = new Article({
        title: '',
        body: ''
      })
    },
    async get () {
      this.article = await Article.find(this.id)
    },
    save () {
      this.article.save()
    },
    delete () {
      this.article.delete()
    }
  }
}
```
ActiveRecordっぽい感じですね。
オブジェクトベースのAPIでLaravelにリクエストできるようにしました。

すこし長いですがこの`Article`オブジェクトは以下のようなコードです。

```Article.js
import Model from './Model'
import MediaFile from './MediaFile'

class Article extends Model
{
  static table = 'articles'
  columns = ['title', 'body', 'description', 'thumbnail_id', 'is_published', 'published_at']
  tags = []
  thumbnail = null

  buildData () {
    let data = super.buildData()

    data['tags'] = this.tags.map(tag => tag.id)
    data['thumbnail_id'] = this.hasThumbnail() ? this.thumbnail.id : -1

    return data
  }

  constructor (data) {
    super()
    super.set(data)
    this.tags = data.tags
    this.thumbnail = new MediaFile(data.thumbnail)
  }

  url () {
    return `/articles/${this.id}`
  }

  previewUrl () {
    return `${this.url()}/preview`
  }

  hasThumbnail () {
    if (this.thumbnail && this.thumbnail.isNew() === false)
    {
      return true
    }

    return false
  }

  releaseThumbnail () {
    this.thumbnail = null
  }
}

export default Article
```

予め`Model`というクラスを作っており、そのModelクラスを継承してRESTfulAPIのパスを指定すればすぐに使えるようになっています。

こうすればリレーションもオブジェクトベースでアクセスできますし、一石二鳥です。

## Object.assignが深い部分までコピーしてくれない
JavaScriptのオブジェクトは基本的に参照が代入されるのでオブジェクトをコピーする際には`Object.assign`関数を持ち入ります。

```sample.js
let a = { value: 10 }
let b = Object.assign({}, a}

a.value = 20

console.log(a) //20
console.log(b) //10
```

それで何が問題かというとオブジェクトにオブジェクトのプロパティーがあっても`Object.assign`はそこまでコピーしてくれない、というものです。

```sample.js
let a = { obj: { value: 10 } }
let b = Object.assign({}, a)

a.obj.value = 20

console.log(a.obj.value) //20
console.log(b.obj.value) //20 上と参照が同じ
```

そこで自分で関数を用意するのも良かったのですが、`lodash`というライブラリに`cloneDeep`という関数があったのでそれを利用してみました。

```sample.js
import { cloneDeep } from 'lodash'

let a = { obj: { value: 10 } }
let b = cloneDeep(a)

a.obj.value = 20

console.log(a.obj.value) //20
console.log(b.obj.value) //10
```

`lodash`いいですね。ドキュメント見てても飽きない。

# 目標
まだ目標が定まっていませんが

- 追加したい機能
    - Unsplash連携
    - Wordpressで書いた記事の移行システム
    - SEO
    - テーマ管理
- その他
    - OSS
        - ある程度機能を作ってソースのリファクタリングをしたらGithubで公開する予定です。

# 最後に
まだNuxt.jsとLaravelの機能を使いこなせていない部分もありますが、このCMSの開発でいろいろなことを学べましたし、これからももっと知識を得ていきたいと思います。

良ければアドバイスなどコメントしていただけると幸いです。

長かったですが最後まで読んでくださってありがとうございました。