GO + Revelで開発していたものをLaravel + nuxtで置き換える

## Goでの小さいweb開発について

依頼されて開発をして、3年近く小さめのwebサイトをGo + Revelで運用してきて正直そこまで恩恵を受けれなかったように感じる。ので以下のように構成変更した。

---

## Before

- 言語: Go v1.9
- パッケージ管理: dep
- フレームワーク: Revel v0.21.0
- ORM: GORM
- ミドルウェア: なし、cacheはrevel cache
- DB: MYSQL
- 開発環境: Docker
- CI/CD: CircleCI

こうなるまでにも3年間でいろいろしたんですが、いろいろ問題がありました。

---

## Beforeの問題点

- Revelが思ったほど盛り上がらなかった
    - GoでのフルスタックなMVCフレームワークとして前は盛り上がってたけど・・・
    - Web開発のためのモジュール追加があまりなかったので自前で実装した部分がとても多かった
- Goの変遷がめんどかった
    - Goを取り巻く環境はバージョンのアップデートともにパッケージ管理や開発環境のアップデートが必須でした。
      あんま本質的ではない保守みたいのがどうしても増えた
    - 2ヶ月に機能追加しようとすると新しく作った環境でgoのバージョン固定に失敗しててアップデートで2日かかったり
- GORMが使いにくかった
    - いまいちDSL的な部分がわかりにくくて結局直接SQL書いた部分が多くて本質的じゃなかった
- 管理画面を作りたいみたいなケースでちょうどいい仕組みが見つからなかった
    - 結局自前で実装した
- セキュリティ等
    - Revelはそこまで考えてくれない
    - CSRFの対策なども別パッケージである
- Revelが各種サービスにおいて標準的じゃない故に
    - Azureのデプロイとかで自前でpowershellを書いた
- Debugしにくい
    - 結局[fresh](https://qiita.com/po3rin/items/9acd41ef428436335c97)でホットリロードしつつ、
      [delve](https://qiita.com/YasunoriGoto1/items/abd0d23262a72e2be9bf)でデバッグをローカルのdocker上で動いているアプリケーション対してうまく適用できなかった・・・

---

## Beforeの良かった点

- Goの恩恵
    - 標準ライブラリの強さ・優秀さ
    - シングルバイナリデプロイ
    - **「Go書いている」**という気分の良さ
- stripeの決済系の実装などはSDKもあってサンプルもなぜかGoが結構あり良かった

---

## 帰ろう、俺たちのPHPへ

--- 

## After

- 言語: PHP v7.2
- パッケージ管理: composer
- フレームワーク: Laravel (Laravel admin)
- ORM: Eloquent (Laravel)
- ミドルウェア: redis
- DB: MYSQL
- フロントエンド: vue + nuxt.js
- 開発環境: Docker(Laradock)
- CI/CD: CircleCI

だいたい2週間くらいで実装は終わりました。

---

## Afterのいい点

---

500年ぶりにちゃんとPHP書いたんですがLaravel便利すぎて泣きそうでした。

### [Laradock](https://qiita.com/J_Shell/items/695a30fd38444d065ae5)

ミドルウェア含むLaravelの動く環境をすぐ作れるし必要なconfファイルなどももろもろ手に入る。
buildしてupしたら動くので瞬殺である。

### [Laravel admin](https://qiita.com/pikonori/items/a04066010e7380b3ee38)

これがあるだけでLaravelにしたくらい便利。CRUDが

> php artisan admin:make StaffController --model=App\\Staff
    
でroutingするだけで簡単なものがすぐ作れる、ロールの設定やリソースアクセスやS3へのアップロード系のプラグインも入っているのでもう脳がいらない。

### [Laravel Passport](https://readouble.com/laravel/5.6/ja/passport.html)

認証までデフォルト実装されている。
Goの時はいいライブラリが見つからなくてがんばって自前でauthとかtoken管理とか書いたので泣いた。

---

## AfterのFrontendが特にいい

---

Afterを支えたフロントエンド技術スタック

- Vue
- Nuxt
- apiblue print
- drakov
- aglio
- Atomic components

一部複雑になったjsの内部でjqueryが使っており、紐解く元気が無くてこっそりjqueryが動いている・・・

---

## nuxt.js

Laravelのプロジェクト内に `client` ディレクトリがあり、そこにフロントエンドリソースが入っている。
nuxt.jsはvueを使ってサーバサイドレンダリング可能なSPAなどのweb appliactionを実装するためのフレームワークである。

今回のプロジェクトの内部は以下のようになっている。

```
% tree -L 2                                                                                                                                                                                                                                                           [10:27:15]
.
├── README.md
├── api-blueprint // localhost:3001でblueprintが、localhost:9080でmock apiが動く
│   └── src
├── assets // webpackで管理されるリソース
│   ├── README.md
│   ├── css
│   ├── icon
│   └── img
├── components // vueのパーツたち
│   ├── AppLogo.vue
│   ├── README.md
│   ├── atoms
│   ├── molecules
│   ├── organisms
│   └── templates
├── layouts // pegeを構成する共通テンプレート
│   ├── README.md
│   ├── default.vue
│   ├── error.vue
│   └── static.vue
├── middleware
│   └── README.md
├── nuxt.config.js
├── package-lock.json
├── package.json
├── pages // 構成によって自動でルーティングが決まる
│   ├── README.md
│   ├── company
│   ├── creators
│   ├── guideline
│   ├── index.vue
│   ├── list
│   ├── movies
│   ├── privacy
│   └── user
├── plugins 
│   ├── README.md
│   └── axios.js
├── static // webpackで管理されないリソース
│   ├── README.md
│   ├── assets
│   └── favicon.ico
└── store // vuexでいうstate
    ├── README.md
    └── user.js
```

---

## nuxtによる恩恵: 構造化されたテンプレート

##### 1. ルーティング

nuxtによるSPAはディレクトリ構造から自動でルーティングが決まる。

```
例）localhost/movies

- pages
    - movies
        - index.vue
        
例）localhost/movies/1

- pages
    - movies
        - _id.vue

```


##### 2. テンプレート

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31237/1d48abf6-3b94-d7ac-1393-8d9de3c42e93.png)


例えば以下のようなdom構造のページに対して

```
- head
    - meta1
    - meta2
- body
    - header
    - section1
        - content1
        - content2
    - footer

```

nuxtでは以下のように分類して実装が可能になる


```
- head              // nuxt.config.jsに記述
    - meta1         // nuxt.config.jsに記述
    - meta2         // nuxt.config.jsに記述
- body              // layout/default.vueに記述
    - header        // components/melocules/Header.vue
    - section1      // components/template/Section1.vue
        - content1  // components/melocules/Content1.vue
        - content2  // components/melocules/Content2.vue
    - footer        // components/melocules/Footer.vue

```

それぞれのコンポーネントを分離してCSSのカプセル化、ロジックの分類が可能になる。

```
module.exports = {
  env: {
    nodeEnv: process.env.NODE_ENV,
    apiBaseURL: apiBaseURL
  },
  /*
  ** Headers of the page
  */
  head: {
    title: 'client',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'Nuxt.js project' },
    ],
    link: [
      { rel: 'shortcut icon', type: 'image/x-icon', href: '@/assets/img/favicon.ico' },
      { rel: 'apple-touch-icon', type: 'image/x-icon', href: '@/assets/img/apple-touch-icon.png' }
    ]
  },
  css: [
    { src: '@/assets/css/main.css'},
  ],

....
```

---

## API Mockの自動化

開発時にAPIを実際に作る前にフロントからモックのAPIを叩ける状態を作りつつAPIドキュメントを作りたい。
正直swaggerはね、めんどくさいんですよ我々フロントエンジニアには・・・

- aglio
    - API Blueprintをファイルを分割しつつもいい感じにmarkdownで書ける
- drakov
    - API Blueprintのmdからmock apiを起動する

＊ただdrakovは複数ファイルに対応してないので、aglioで以下のようにビルドして対応

```
// blueprintの起動
"blueprint": "aglio -i ./api-blueprint/src/index.apib --server ",

// mockのビルド、drakovで参照するまとめたファイルを作る
"apimock-build": "aglio -i ./api-blueprint/src/index.apib -c -o api-blueprint/src/compiled.apib ",

// まとまったファイルからmock api起動
"apimock": "drakov -f 'api-blueprint/src/compiled.apib' --watch -p 9080  --autoOptions"
```

---

## ルーティング

SPAとしてのJSによるルーティングとPHPによるルーティングが混在する問題がある。
PHPはAPIのみを提供するようにしたかったけど、Facebookログインのコールバックとかでどうしても口を用意しないといけなかったので以下のようにした。

```
Route::get('/logout', 'Auth\SocialLiteController@logout');
Route::get('/auth/signin', 'Auth\SocialLiteController@signin');
Route::get('/auth/login', 'Auth\SocialLiteController@login');
Route::get('/auth/callback', 'Auth\SocialLiteController@callback');

Route::get('/{any}', function () {
    return view('spa');
})->where('any', '.*');
```

基本的には全てSPAで受けてvue-routerでルーティングする。これは基本的にSPAがベースであるのでいけるが、逆の場合は結構めんどくさい・・・

---

## axios + Laravel

axiosはモダンなJSのAPI clientである。
Laravelでもサポートされており、CSRFの対策などがデフォルトで入るようになっている。

> Tip!! デフォルトでAxios HTTPライブラリにより、csrf-tokenメタタグの値が、resources/assets/js/bootstrap.jsへ保持されます。このライブラリを使用しない場合、自分のアプリケーションでこの振る舞いを実現する必要があります。

他の選択肢などないのでは・・・
ただこれはあくまでLaravel Mixを使ってLaravel/resources配下をちゃんとビルドしている前提であり、nuxtが別で管理されているとビルド忘れがち。

--- 

## その他の工夫

---

- seedはCSVからできるようにした
- Factoryからもできるようにした


くらいか・・以上。
やはりデプロイとかでいうとビルド環境と稼働環境が前者さえあれば後者には余計なセットアップが入らないので楽だったけど、前者が結構手間だった感じ。PHPにすると両方それなりだが圧倒的低い知識でいいので楽。