サーバーサイドエンジニアによる完全無料SSPA30分クッキング


# はじめに

長い間サーバーサイド専属エンジニアをしていたので、フロント復帰の意思表示として
[Vue.js](https://jp.vuejs.org/index.html) & [ReactiveX](http://reactivex.io/) & [element-ui](https://element.eleme.io/#/en-US) & [Firebase](https://firebase.google.com/?hl=ja) で
**完全無料**サーバーレスシングルページアプリケーションを作成してみました。
最近ダイエット中なので、日々食べたものを登録していくアプリケーションにしてみようと思います。

フロントご無沙汰サーバーサイドエンジニアにどこまでできるのか・・・

## Vue.js環境作成
vue-cliでプロジェクトの作成を行います。  

```shell
# vue-cliのインストール
yarn global add @vue/cli 

# project作成
# 1. manualを選択
# 2. javascriptを選択
# 3. In package.jsonを選択
# 4. Use Yarnを選択
vue create sample

# projectの起動
cd sample
yarn serve
```

コレでプロトタイプは完成。
簡単！！

## firebaseのプロジェクト作成
[トップページ](https://firebase.google.com/?hl=ja)より下記手順でfirebaseプロジェクト作成を行います。

1. プロジェクトの追加
1. ウェブアプリにFirebaseを追加、でスクリプトタグを取得する
1. Databaseを使ってみる
1. Cloud Firestore Betaを選択
1. ロックモードで開始  
いったんfirebase console上で動作確認をしたいだけのため、きついセキュリティで開始
1. コレクションを追加
1. 食べたものを管理するアプリを作ると想定して、foodsというコレクション名にする
1. データの追加
1. 画面上で保存されていることを確認

## javascriptでfirebase検索
frontendプロジェクト配下で下記のコマンドを実行しfirebaseをインストール

```
yarn add firebase
```

とりあえずfirestoreのfoods配下のアクセス権限を与える

```
service cloud.firestore {
  match /databases/{database}/documents {
    match /foods/{food} {
      allow read: if true;
      allow write: if true;
    }
  }
}
```

■javascript
フロントの適当な箇所に下記のコードを記述して
コンソールログに登録済みデータが出力されることを確認

```js:food-list.js
var firebase = require('firebase')
require('firebase/firestore')
// Initialize Firebase
var config = {
  apiKey: "<API_KEY>",
  authDomain: "<PROJECT_ID>.firebaseapp.com",
  databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
  storageBucket: "<BUCKET>.appspot.com",
  messagingSenderId: "<SENDER_ID>",
}
firebase.initializeApp(config)

var db = firebase.firestore()
db
  .collection('foods')
  .get()
  .then(qst => {
    qst.forEach(doc => {
      console.log(doc.data())
    })
  })
```

## リスト表示
vue-rx,rxjs,element-uiのインストール

```
yarn add vue-rx rxjs element-ui
```

■html

```html:food-list.html
<el-button type="primary" icon="el-icon-search" v-stream:click="plus$">Search</el-button>
<el-table v-loading="loading" :data="foods" style="width: 100%">
  <el-table-column prop="名前" label="名前" width="280">
  </el-table-column>
  <el-table-column prop="タンパク質" label="タンパク質" width="280">
  </el-table-column>
  <el-table-column prop="脂質" label="脂質" width="280">
  </el-table-column>
  <el-table-column prop="炭水化物" label="炭水化物" width="280">
  </el-table-column>
</el-table>
```

■javascript

```js:food-list.js
import Vue from 'vue'
import Rx from 'rxjs/Rx'
import VueRx from 'vue-rx'

Vue.use(VueRx, Rx)
export default {
  name: 'FoodList',
  props: {
    msg: String
  },
  subscriptions: {
    foods: Rx.Observable.fromPromise(
      db
        .collection('foods')
        .get()
        .then(qst => qst.docs.map(doc => doc.data()))
    )
  }
}
```

## 新規追加/編集処理
食事を登録する画面を作ります。

■html

```html:edit-food.html
<span class="input-label">名前</span>
<el-input v-model="name"></el-input>
<span class="input-label">タンパク質</span>
<el-input v-model="protein"></el-input>
<span class="input-label">脂質</span>
<el-input v-model="lipid"></el-input>
<span class="input-label">炭水化物</span>
<el-input v-model="carbo"></el-input>
<span class="input-label">食べた日</span>
<el-input v-model="eatDate"></el-input>
<el-button v-if="$route.params.id" v-stream:click="setFood$">更新</el-button>
<el-button v-else v-stream:click="addFood$">新規追加</el-button>
<el-dialog title="Tips" :visible.sync="dialogVisible" width="30%">
  <span>登録完了しました</span>
  <span slot="footer" class="dialog-footer">
    <el-button type="primary" @click="dialogVisible = false">確認</el-button>
  </span>
</el-dialog>
```

■javascript

```js:edit-food.js
const db = firebase.firestore()
const addPms = obj =>
  Rx.Observable.fromPromise(
    db.collection('foods').add({
      名前: obj.name,
      タンパク質: obj.protein,
      脂質: obj.lipid,
      炭水化物: obj.carbo,
      食べた日: obj.eatDate
    })
  )
const setPms = obj =>
  Rx.Observable.fromPromise(
    db
      .collection('foods')
      .doc(obj.id)
      .set({
        名前: obj.name,
        タンパク質: obj.protein,
        脂質: obj.lipid,
        炭水化物: obj.carbo,
        食べた日: obj.eatDate
      })
  )

const foodPms = id => {
  if (id) {
    return Rx.Observable.fromPromise(
      db
        .collection('foods')
        .doc(id)
        .get()
        .then(doc => doc || { data: {} })
        .then(doc => doc.data())
    )
  } else {
    return Rx.Observable.empty()
  }
}

export default {
  name: 'EditFood',
  props: {
    msg: String,
    id: String
  },
  data() {
    return {
      name: '',
      protein: '',
      lipid: '',
      carbo: '',
      eatDate: '',
      dialogVisible: false
    }
  },
  methods: {
    clear() {
      this.name = ''
      this.protein = ''
      this.lipid = ''
      this.carbo = ''
      this.eatDate = ''
      this.dialogVisible = true
    },
    initialize(obj) {
      this.name = obj.名前
      this.protein = obj.タンパク質
      this.lipid = obj.脂質
      this.carbo = obj.炭水化物
      this.eatDate = obj.食べた日
    }
  },
  domStreams: ['addFood$', 'setFood$'],
  mounted() {
    foodPms(this.$route.params.id).subscribe(this.initialize)
    this.addFood$
      .flatMap(() =>
        addPms({
          name: this.name,
          protein: this.protein,
          lipid: this.lipid,
          carbo: this.carbo,
          eatDate: this.eatDate
        })
      )
      .subscribe(o => this.clear())
    this.setFood$
      .flatMap(() =>
        setPms(
          Object.assign(
            {
              id: this.$route.params.id
            },
            this
          )
        )
      )
      .subscribe(o => this.clear())
  }
}
```

## 削除処理
リスト表示の画面にボタン追加しました

■html

```html:food-list.html
<el-table-column label="処理">
  <template slot-scope="scope">
    <router-link :to="{ name: 'edit', params: { id: scope.row.id }}" tag="el-button">Edit</router-link>
    <el-button v-stream:click="{ subject: delete$, data: scope }">Delete</el-button>
  </template>
</el-table-column>
```

■javascript

```js:food-list.js
const deletePms = id =>
  Rx.Observable.fromPromise(
    db
      .collection('foods')
      .doc(id)
      .delete()
      .then(console.log)
  )

export default {
  //... 略
  subscriptions() {
    const deleteOps = this.delete$.pluck('data', 'row', 'id').flatMap(deletePms)
    const searchOps = this.plus$
    return {
      foods: Rx.Observable.merge(deleteOps, searchOps).flatMap(foodsPms)
    }
  }
}
```

## 公開
ここまで来て力尽きたので公開。
[firebase hosting](https://firebase.google.com/docs/hosting/quickstart?authuser=0)を使います。
手順はリンク先のまんま。

## 動作
![front.gif](https://qiita-image-store.s3.amazonaws.com/0/93775/3a53b4b3-24a8-7221-a738-91095b5785f2.gif)


# 総括
Vue.jsもfirebaseもとても使い勝手がよく理解しやすいですね。
ただ、やりたかったことの半分もできておらず。。。
個人的課題としては

#### デザイン
* UIフレームワークの知識習得

#### javascript
* ライフワークの把握  
どの処理をどこに書くべきかが曖昧
* パッケージわけをどのようにすべきか知りたい
* ReactiveXのAPIもっと自由自在に使えるようになりたい。  
doメソッドとかなぜかエラーになって使えなかった。

#### firebase
* 認証
* 通知
* 権限管理


ここらへんもっとナレッジをためたいと思いました。
参考サイト等教えてもらえたらうれしいです。