Firebaseで完結するリッチなWebアプリ構築の勘所

先日、Togetter社長の@yositosiさんとひょんなことからお友達になり「なんかFirebase使って面白いことやろうよ」という話になったので一緒に面白いことをやりました。

この記事は、

- Firebaseを使うと**何ができるのか**
- Nuxt.js/Vue.jsとFirebaseの**勘所**
- Firestoreでの**複雑なクエリ処理**にどう対応するか

などのトピックを中心に紹介していければと思っています。

# [なんとかPay](https://xn--pay-k73byfl10a.com/)
[Togetter](http://togetter.co.jp/)の社長の[@yositosi](https://twitter.com/yositosi)さんからFirebaseを使ったアプリ構築の話を頂きお手伝いさせていただいた、エイプリルフールの企画の「[なんとかPay](https://xn--pay-k73byfl10a.com/)」というサービス。誰でも自由にPayを発行できる、昨今のPay蔓延している社会に物申しているようなそうでもないようなそんなサービス。

今回は技術周りで色々とお手伝いをさせていただきました！

### 自分だけのPayが作れる
[<img src="https://qiita-image-store.s3.amazonaws.com/0/262161/4774b502-5a87-005e-b8a6-7817cef5eb5d.png" alt="image" width="70%">](https://xn--pay-k73byfl10a.com/)

### リアルタイムで状況が更新される
[<img src="https://qiita-image-store.s3.amazonaws.com/0/262161/6f73605d-95bf-8541-4002-87c098c2285b.gif" alt="image" width="70%">](https://xn--pay-k73byfl10a.com/)

### こんな感じでOGP画像も生成される
[<img src="https://qiita-image-store.s3.amazonaws.com/0/262161/d1596a0b-3544-083e-29c1-6e2610023354.png" alt="image" width="70%">](https://xn--pay-k73byfl10a.com/)

# 自己紹介
モノづくりをしたりプログラミング先生をやったり。最近はReactNative触ったり色々。

- Railsで[**歌声専門フリマサイト**](https://utaile.com/)
- React勉強で[**今期アニメ検索アプリ（Qiita記事）**](https://qiita.com/y_kawase/items/8f1b5a303400a09c4923)
- Nuxt×Firebaseで[**文字を画像に変換するアプリ（Moji → Pic）**](https://mojipic.f-arts.work/)（Qiita記事は[こちら](https://qiita.com/y_kawase/items/fb9f747d88af151f981a)）

とか作ったりしてます。また、Web系言語やらのネタで[**ブログにTIPS**](https://blog.f-arts.work/)を書いたり、[**Twitter(@y_kawase)**](https://twitter.com/y_kawase)でもちょこちょこ真面目ネタも呟いているので宜しければ絡んで下さい。お仕事依頼もお気軽に絡んで下さい。


ゆるいネタで**noteも始めてみた**ので、もし興味があればこちらもご覧いただけると嬉しいです。

- [お前はThunderbolt3を何も知らない。USB Type-Cに翻弄されないための教養。](https://note.mu/ykawase/n/n23deaa2a0ef2)


# 構成など
Nuxt on Firebaseな感じでやっています。細かい構成は大体以前書いていた記事の構成とも被るのでこちらもぜひ → [Webアプリ無料運営のススメ：FirebaseとNuxt(Vue)なら最強！](https://qiita.com/y_kawase/items/fb9f747d88af151f981a)

若干はしょってますが、こんな感じの構成です。Firebaseで完結とか言いながら、定期タスクの実行に[**Google Cloud Scheduler**](https://cloud.google.com/scheduler/)こっそりつかってます。これはいわゆる**cron的なやつ**。HTTP叩くとかPub/Sub叩くとかできるので、GCP系で開発している方はぜひ使うとよい。（最短1分間隔jobを作れて、3jobまで無料）

![NantokaPay (4).png](https://qiita-image-store.s3.amazonaws.com/0/262161/60d73d07-088d-9051-4b11-04b576aec248.png)

　
**ーー追記ーー**
FunctionでのSSRは少し古い内容になります。先日（2019/4）に発表されたCloudRunを使ってSSRをするのが今後は主流になるかと思われます。
https://firebase.google.com/docs/hosting/cloud-run
　


使ったライブラリでメインのところはこのあたり。

- nuxt
- vuexfire / vuexとfirebaseをうまく融合
- twemoji / 絵文字
- node-qrcode / QRコード生成
  - 検索で上に出てくる太古のqrcodeライブラリ使わないように注意。日本語とかに対応してないから。新しいのをつかうよう。
  - https://github.com/soldair/node-qrcode
- dayjs / momentより軽い時間周りの処理
- base64url / Payの名前をIDに変換したい時に使った
  - 後述してますが、日本語ID名だとバグる時に、一意な変換をしたかったのでbase64url使いました。
- puppeteer / Function側でOGP画像を作る時に使った。
  - puppeteer内部でcanvas描画したやつをスクショ的な感じ。ただ、実装が大変だし、Functionも重いので、クライアント側で作ってstorageにアップが楽
  - ただ、端末依存しない画像が作れるメリットは大きい（端末依存云々の話は、[以前私が作った文字画像変換アプリでも話題になっていた話](https://qiita.com/y_kawase/items/fb9f747d88af151f981a#konva%E3%81%AE%E6%96%87%E5%AD%97%E9%85%8D%E7%BD%AE%E3%81%AF%E7%92%B0%E5%A2%83%E5%B7%AE%E5%88%86%E3%81%8C%E3%81%82%E3%82%8A%E3%81%9D%E3%81%86)です）

# FirebaseとNuxtは便利
Firebaseはいいぞおじさんとして最近この話ばっかりしてる気がしますが、サクッとアプリが作れるので、色々作りたいひとにはおすすめしたい。もちろん初心者はRails×Vueとかでもいいとは思うので、そのあたり飽きてきた**中級者あたりは是非**。

昨今流行りのJAM Stackな開発云々の知見含め、**サーバーレス、SPA、SSR、PWAあたりの知識**も一通り手に入るので、腕に覚えのある方は是非触ってみることをおすすめしたい。

## Firebaseを使って良かったところ
### 認証が大変楽に
**このご時世、認証周りを自分で書いてる人なんているんです？**（煽り）というくらい、認証はとは面倒なもの。セキュリティ的にも、自分で頑張って作るより、アリものを使わせてもらうほうが絶対良いです。もちろん勉強したいひとは一から作ったほうがためにはなる。本アプリは、Twitter認証だけでしたが、FacebookやGoogle認証も可能です。最近は[Auth0](https://auth0.com/jp/)もアツいですが今回は割愛。

https://firebase.google.com/docs/auth/?hl=ja

### Firestoreはシンプルながら高機能
Firebase（NoSQLなデータベース）は簡単に**リアルタイムデータ同期**ができます。今回はこれが一番このアプリで効いていたなと思いました。トップページのアクティビティで、誰かがPayを作ったり送金する度に、リアルタイムで状況が更新されるので、見てるだけでも楽しいものになりました。

こんな感じ。

[<img src="https://qiita-image-store.s3.amazonaws.com/0/262161/6f73605d-95bf-8541-4002-87c098c2285b.gif" alt="image" width="70%">](https://xn--pay-k73byfl10a.com/)

使ったライブラリ：[vuexfire](https://github.com/vuejs/vuefire/tree/master/packages/vuexfire)

もちろん他のDBでも似たようなリアルタイム反映は頑張ればできるんですが、サクッと実装できるところがすごい。

## Nuxt使ってて良かったところ
### SSR設定が楽
耳にタコなネタですが、**サーバーサイドレンダリングが簡単**にできます。いわゆるツイートとかした時に、OGP画像がちゃんとページ毎に各々設定されるのとかは、このSSRのお陰でもあります。

### PWA設定が楽
PWA対応してて力入れてるようにみえて、実はNuxt様のプラグイン入れてちょこっと設定ファイル書いただけ。**NuxtはPWAやらSSRやらVuexやら初期状態でも全部入りな感じで好き**。

## Firebaseの取扱で大変だったところ
### Firestoreでユニーク扱いをしたい時
Payの名前で同名を避けたかったのですが、この**ユニーク設定の実装が意外と面倒**でした。RDBMS系だと普通にできるuniq指定などは存在しないので、自分で実装する必要がある。具体的には、

- FirestoreのドキュメントのID名をユニークにしたい名前にする
- トランザクション機能を使って、既存テーブルを検索して存在しなければ追加

のような実装が必要になる。また、ユニークにしたいものを直接ID名にするため、**日本語を取り扱うときは、base64urlなどで適宜ID名をエンコードしておかないと、文字周りで面倒**もあったりする。このへんリレーショナルDB系（MySQLとか）でさわってた人はすごくイライラするとは思う。

https://medium.com/@jqualls/firebase-firestore-unique-constraints-d0673b7a4952



### Firestoreでの権限操作
Firestoreはクライアント側から直接触りに行くので、**、ユーザによる変更権限などの制御が意外と小回りが効きにくくてキツい**イメージがあります。

　
**ーー追記：セキュリティルールでいけた内容だったかも（詳しくはコメントに記載）ーー**
https://qiita.com/y_kawase/items/1de690b40553ef76acb3#comment-02654fd64fa4c26faca9
　

たとえば一例として、**[なんとかPay](https://xn--pay-k73byfl10a.com/)では、それぞれの個々人がPayのお財布を所持**しています。

例えばこんな感じで権限ルールを書いて、money（財布）テーブルの中に持ち主のuid（ユーザID）とかを埋め込んでおけば、

- Aさんの財布は全員見れる（read）
- Aさん自身は財布の中身を出し入れできる（create, update）

```
service cloud.firestore {
    match /databases/{database}/documents {
      match /money/{moneyId} {
        allow read;
        allow create, update: if request.auth.uid == resource.data.uid;
      }
    }
  }
}
```

みたいな感じで、一見すると要件を満たしそうにみえるのですが、

- BさんがAさんの財布に1000円送金する(Aさんデータをupdate)

という操作は**権限的にアウト**になります。なら、他の人もupdateできるようにすればいいだけじゃんというのが自然な考えなのですが、そうすると、今度は、

- BさんがAさんの財布の中身を勝手に抜き取る（Aさんデータをupdate）

みたいなことも可能になってしまうのです。

普通のRailsみたいなサービスだと、そんな処理はControllerに書かねーぞってなるのですが、クライアント側で動いているJavascriptでの挙動＆APIを投げてFirestoreデータをいじるため、**やっちゃうとこのあたりの改竄リスクが高まり**ます。

### Firestoreでの権限操作問題の私なりの結論
先の権限問題が出てくる場面は「Aさんが作ったデータに対して、Bさんが何か処置をしたい」という操作がある場合に出てくる問題です。そういう意味では、**TODO管理アプリとかのような他者が介入しないようなシステムならば、上記のルールベースな制御だけでも全く問題はない**ので、そこだけは勘違いしないように！！！。

ということで、解決策は、

- ユーザは基本的に**readだけ許可**する
- createやupdateといった書き込み系操作については、**Function側に委ねる**

が一番しっくりくるかと思います。つまり何かデータの更新がしたい時は、Bさんは一度Function側のAPIを叩いてもらって、Function側で内容に問題がなければ、Function側が管理者権限でデータの更新をする、といったような流れになるかと思います。

こうすると、各テーブルの書き込み系処理毎にAPIのエンドポイントが必要になってきてしまう問題もあるので、[FirebaseFunctions上にGraphQLサーバーを実装する](https://qiita.com/mizchi/items/8036b20d814921625a76)とかでエンドポイントをまとめるのもいいかなと思いました。（今回はここまではやらなかったのですが）

# まとめ
## 技術まわり
複雑なDB操作が絡んだアプリを作る時には意外とFirestoreがネックになることがありそうでした。Firestoreはクエリなどの発行も制限がキツいので（例えばOR検索できないので各自結合する必要がある、!=のクエリが発行できないなど）、そのあたりとどう折り合いをつけるかという所になるかと思います。 → 参考：[Firestore - クエリの制限事項](https://firebase.google.com/docs/firestore/query-data/queries?hl=ja#query_limitations)


要件がそこまで複雑なものでなければ、実装する時に

- Firestoreに**直の書き込み系を禁止**
- Function上でApolloServer（GraphQL）動かして**複雑なクエリや書き込みをこっちで担う**（query, mutation）
- Apolloのsubscription機能は**Firestoreのリアルタイムアップデートで代用**

っていうやり方に予め確定しておけば、デメリットは消せるかなと思ってたりします（Apolloじゃなくて、普通にFirestoreの機能直書きでもOK）。Function × GraphQLはこのへんが参考になりそう　→ [Firebase Functions 上に GraphQL サーバーを実装する](https://qiita.com/mizchi/items/8036b20d814921625a76)


　
**ーー追記：セキュリティルールでいけた内容だったかも（詳しくはコメントに記載）ーー**
https://qiita.com/y_kawase/items/1de690b40553ef76acb3#comment-02654fd64fa4c26faca9
　


とりあえずはFirestoreは強力ながらもシンプルなデータベースではあるので、とりあえずFirestoreで作り始める前に、**このアプリではどういうクエリを発行するのかを事前に考えた上**で、複雑なクエリ操作が絡みそうなシステムだったら、最初からGCPの「Cloud SQL」を使うのもいいとは思う。

※もちろんCloud SQLだと、時間に対する課金になるので、サーバレスの旨味は減ってしまうとは思うけど。

## サービスまわり
[なんとかPay](https://xn--pay-k73byfl10a.com/)は、エイプリルフールネタとして、また、大喜利のようにして使って遊ぶアプリとして皆様に楽しんでいただいている所はありますが、「LTした人にポイント授与」「毎週頑張った人にポイント付与」みたいな感じでオリジナルな使い方を見出して、**自分だけのポイントサービス的な真面目な使い方**もできるとは思いますので、一度触ってみて下さい。

遊びにもガチで取り組むTogetter社は流石だなという所を今回ヒシヒシと感じました。社風にも現れているのでしょうか転職エントリみてるとめっちゃ楽しそうにしてる。 → [トゥギャッター株式会社に転職しました](https://y-ahiru.hatenadiary.jp/entry/2019/04/06/173107)


## 勉強するときのオススメ

### Vueドキュメント
公式ドキュメント侮ることなかれ、読みやすいです。ちゃんと日本語ですぞ。
https://jp.vuejs.org/v2/guide/

### Nuxtドキュメント
こちらの公式ドキュメントも読みやすい。日本語。
https://ja.nuxtjs.org/guide/

### Firebaseドキュメント
こちらの公式ドキュメントも読みやすい。ただ章立てから欲しい情報が若干見つけにくいときもある。日本語。
https://firebase.google.com/docs/?hl=ja

### 書籍：Vue.js&Nuxt.js超入門
掌田先生著。Vue・Nuxtに加えて外部API連携（Firebaseも少しある）も扱っているので、ガッと体系的に勉強したい時などに大変良い。
[<img src="https://qiita-image-store.s3.amazonaws.com/0/262161/3df9900f-8b19-631a-54ed-af673147e6d8.jpeg" alt="image" width="30%">](https://amzn.to/2Kfee0c)


## さいごに
本記事をここまでお読みいただきありがとうございました。何か素朴な疑問でも、お仕事の依頼でも、何か気になることがあれば**是非**[**Twitter(@y_kawase)**](https://twitter.com/y_kawase)**で絡んで**下さい。
直接ビデオ通話で相談したいぞという方は[このあたり](https://menta.work/plan/933)から。

#### 過去記事・関連記事も宜しければどうぞ

- [Reactで誰もがやりたかった10の機能。アプリ構想はあるけど作れない人の壁をぶっ壊す。](https://qiita.com/y_kawase/items/8f1b5a303400a09c4923)
- [Webアプリ無料運営のススメ：FirebaseとNuxt(Vue)なら最強！](https://qiita.com/y_kawase/items/fb9f747d88af151f981a)
- [Qiita初投稿でも狙ってバズらせられた話『5つの成功と2つの失敗』](https://blog.f-arts.work/archives/857)

ゆるいネタで**noteも始めてみた**ので、もし興味があればこちらもご覧いただけると嬉しいです。

- [お前はThunderbolt3を何も知らない。USB Type-Cに翻弄されないための教養。](https://note.mu/ykawase/n/n23deaa2a0ef2)