Cloud Functions for Firebaseが最高だった話

Life is Tech! でメンターをしています、ほりーといいます。昨日のみーたん([@MII-CHANG](https://qiita.com/MII-CHANG))の記事、めっちゃ楽しそうでしたね。これを書き終わったら僕もトライしてみようと思います。

さて、AdventCalendar初参加です。
題材には、Cloud Functions for Firebaseを選択してみました。普段JavaScriptは書かない僕ですが、これを期に挑戦してみたいと思います。


# はじめに
普段Androidアプリをつくっていて、「あぁサーバー側で色々やってくれたら楽なのに...」と感じることは多々あります. そこで自分でサーバー側のプログラムを書こうとしても、環境構築などいちいち手間が多いのがネックなんですよね. 「もっと手軽にサーバー側のプログラム動かす環境つくれないの！？」といつも不満でした. 

色々物色してて見つけたのが サーバーレスな**"Cloud Functions for Firebase"**. 試してみて「これはめっちゃ使える！！」と感動した次第です. 

似たようなサービスに、Amazon Web Serviceの "AWS Lambda" があって, もちろんこっちのほうが先発のサービスなんですけど, AWSの方は説明も多いし覚えることが多いし（めんどくさがり）で, とても敷居が高いままでした. 

そんなときにふと見つけた Cloud Functions for Firebase の文字。Googleのドキュメントも（やっぱり）読みやすい！！ これだ！！こっちのほうが絶対いい！！というわけで試してみました。

# 簡単に作ってみる
じゃあ早速抑えて置きたい基本機能を作ってみます。その前に、Cloud Functionの[チュートリアル](https://firebase.google.com/docs/functions/get-started?hl=ja)から最初の設定を行っておきましょう。

それでは行きましょう。

## 1. GET リクエストで Hello World を表示してみる。
お決まりの Hello World も簡単に出来ます。

```index.js
const functions = require('firebase-functions');

exports.hello = functions.https.onRequest((request, response) => {
  response.status(200).send("Hello World")
})
```
簡単ですね
[チュートリアル](https://firebase.google.com/docs/functions/get-started?hl=ja)に従ってデプロイしてみましょう。

デプロイ後は、`https://us-central1-◯◯◯◯.cloudfunctions.net/hello`にGETリクエストを送れば、

```
Hello World
```

と返ってきます。

## 2. クエリを使ってみる
リクエストにクエリも付けてみたいですね。`http://~~~~~~~~~~~?query_param=ABCD`の部分を取得してみましょう。
リクエストを送ったら、送った名前にあいさつしてもらいましょう。

```index.js
exports.hello = functions.https.onRequest((request, response) => {
  if (request.query.name !== undefined) {
    let param = request.query.name
    response.status(200).send("Hello " + param)
  } else {
    response.status(200).send("Hello World")
  }
})
```

`https://us-central1-◯◯◯◯.cloudfunctions.net/hello?name=HALU5071`にリクエストを送れば、

```
Hello HALU5071
```
と返事が来ますね！！


## 3. /hello以下の文字を取得
今度は、エンドポイントなどの`/hello/◯◯◯`というパラメータ部分を取得してみましょう。

```index.js
request.params[0].slice(1)
```
と書いてあげれば、◯◯◯が取得できます。これを使ってあげれば

```index.js
exports.hello = functions.https.onRequest((request, response) => {
  if (request.params[0] !== undefined) {
    let param = request.params[0].slice(1)
    response.status(200).send("Hello " + param)
  } else {
    response.status(200).send("Hello World")
  }
})
```

として、`https://~~~~/hello/HALU5071`にリクエストを送れば、

```
Hello HALU5071
```
と返ってきますね

## 4. GET, POSTの処理をわける
GETのリクエストが出来たら、作りたくなるのはPOSTリクエストですね。（[GETとPOSTの違いについて](https://qiita.com/sou_world/items/522efb74421255f0e0a1)）

```index.js
exports.hello = functions.https.onRequest((request, response) => {
  switch (request.method) {
    case 'GET':
      // ここにGETの処理
      break
    case 'POST':
      // ここにPOSTの処理
      break
    default:
      response.status(400).send({ error: 'Something blew up!' })
      break
  }
})
```

このように分岐してあげましょう。同様に、`PATCH`も`DELETE`も出来ますよ


# 応用を考えてみる
この基本機能を使って応用を考えてみましょう。これだけあれば、ちょっとしたものはつくれそうです。
## 応用例1. Realtime Databaseと連携する。 
Cloud Functions はFirebaseのその他機能との連携がとても簡単にできます。その際、GoogleCloudPlatformの課金設定が必要です。12ヶ月300ドル分の無料トライアル枠がありますので気軽に試してみましょう。

まずは、firebaseの機能を使うためにモジュールを追加してあげます

```index.js
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
```

これでfirebaseの機能が使えるようになりました。

### 取得
ここから、データベースの特定のノード以下のデータを1回だけすべて取得するには、

```index.js
admin.database().ref('/hogehoge/fugafuga')
      .once('value')
      .then(data => {
        response.status(200).send(data)
      })
      .catch(error => {
        response.status(404).send({ message: 'Not Found' })
      })
```

これだけですね！`ref()`内でどのノード以下のデータを取得するか指定してあげるだけです。

ここから、たとえば「IDが`1234`のものだけ」取得する場合には、データを並べ替える`orderByChild()`を使ってあげて、

```index.js
admin.database().ref('/hogehoge/fugafuga')
      .orderByChild('id').equalTo('1234')
      .once('value')
      .then(data => {
        response.status(200).send(data)
      })
      .catch(error => {
        response.status(404).send({ message: 'Not Found' })
      })
```

としてやれば、お目当てのデータだけ取れます。この際、データベース側でindexをはる必要があるので注意してください（[データのインデックス作成](https://firebase.google.com/docs/database/security/indexing-data?hl=ja)）


### 保存
データベースに保存する場合には、`.set()`もしくは`.push()`を使ってあげましょう。つまりこうなります。

```index.js
admin.database().ref('/hogehoge/fugafuga')
  .push(jsonStr)    // ここが保存したいデータ
  .then(snapshot => {
    response.status(201).send({ message: 'new Data created' })
  })
  .catch(error => {
    response.status(418).send({ 'error': error })
  })
```

参考 : [公式リファレンス](https://firebase.google.com/docs/database/web/read-and-write)

## 応用例2. 端末から送ったデータで他の端末に通知を送る
<p align="center">
  <img src="https://qiita-image-store.s3.amazonaws.com/0/87489/3f1d8eee-9e5b-1c35-a91a-c8c40585acda.png" width=600 />
</p>

通知を送ることも出来ます。Firebaseの機能の一つに、端末に通知を送る機能があるのでこれを使ってあげましょう。

アプリ側の設定も必要なので、公式のリファレンスに従って端末を設定してあげます。
Android : [Android アプリでメッセージを受信する](https://firebase.google.com/docs/cloud-messaging/android/receive?hl=ja)
iOS : [iOS アプリでメッセージを受信する](https://firebase.google.com/docs/cloud-messaging/ios/receive?hl=ja)

端末側の準備は完了です。これらの設定によって、個々の端末を識別する「登録トークン」が取得できるので、これらをデータベースに保存しておくなりして後で使いましょう。

では、Cloud Functions 側では、保存した登録トークンをもとに通知をしてあげます。

```index.js
let sendMessage = function() {
  // 登録トークン
  let token = "dnTT0Pm........hSvvccxscYuLd"

  // 通知の内容をここで定義
  let payload = {
    notification: {
      title: "Hello HALU5071",
      body: "This is node.js messaging sample"
    }
  }

  // messaging()を呼び出して通知を送る
  admin.messaging().sendToDevice(token, payload)
    .then(response => {
      response.status(200).send({ message: 'Successfully sent notification' })
    })
    .catch(error => {
      response.status(400).send({ error: 'Failed to send notification' })
    })
}
```
通知を送るエンドポイントを用意してこの関数を呼んであげさえあげれば、

<img src="https://qiita-image-store.s3.amazonaws.com/0/87489/8c41ba61-5468-ec31-566d-cfe1821210fb.png" width=500>

通知が来ましたね！！
端末の操作でいくらでも通知が送れそうです。


# 最後に
[公式のサンプル](https://github.com/firebase/functions-samples)にもたくさんのサンプルがありますので一度見てみると良いでしょう。`url-shortener`とか、`github-to-slack`とか面白そうですね

さて、明日はLife is Tech! iPhoneコースが誇る巨人、しのきんさん（@ShinokiRyosei）ですね！記事楽しみです！

※ この記事は[Firebase Functions が最高だった話](https://medium.com/@yasunorihorii/firebase-functions-%E3%81%8C%E6%9C%80%E9%AB%98%E3%81%A0%E3%81%A3%E3%81%9F%E8%A9%B1-9cd7d352185e)に追記したものになります. 

# 参考
- [GETとPOSTの違いについて](https://qiita.com/sou_world/items/522efb74421255f0e0a1)