酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Cloud Functions for FirebaseでFirestoreのデータを返す

Cloud Functions for Firebase (Firebase Functions)で、Firestoreに格納しているデータをJSON形式で返せるか試してみました。

Cloud Functions for Firebaseをデプロイする

Cloud Functions for Firebaseをデプロイするところまでを紹介します。任意のディレクトリで作業します。

firebase init

ウィザードに従って雛形を作成します。

ここでは既存のプロジェクトを選択して、TypeScriptを選択しました。この時点でディレクトリ構造は下図のようになりました。

f:id:ch3cooh393:20191231171633p:plain

functions/src/index.ts をエディタで開いて、helloWorldのコメントを外します。

import * as functions from 'firebase-functions';

// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
export const helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});

必要なライブラリをインストールして、Firebaseへデプロイします。

cd functions
npm install
cd ..
firebase deploy --only functions

ブラウザで https://us-central1-PROJECT_NAME.cloudfunctions.net/helloWorld にアクセスすると、Hello from Firebase! と表示されました。

この記事を書いてから気付いたけど、2年前にもFirebase Functionsに挑戦した痕跡が見つかった。当時からあまり変わってなさそう。

FunctionsでFirestoreのデータを読み出す

つづいて、Cloud FunctionsでFirestoreに格納されているデータを読み出してJSONをレスポンスとして返したいです。

構造は仮に下記のようになっているとします。

f:id:ch3cooh393:20191231173917p:plain

Firebase Console上では、データはこのようになっています。

f:id:ch3cooh393:20191231173102p:plain

f:id:ch3cooh393:20191231173105p:plain

Firebase Admin SDKの初期化に関しては「Add the Firebase Admin SDK to Your Server  |  Firebase」をみます。アクセスキーを用意してローカルでGOOGLE_APPLICATION_CREDENTIALSにパスを通しておきます。

export GOOGLE_APPLICATION_CREDENTIALS="/Users/PATH_TO/serviceAccountKey.json"

functions/src/index.tsgetPrices でアクセスされた場合のフック*1を追加します。

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

admin.initializeApp({
  credential: admin.credential.applicationDefault(),
  databaseURL: "https://PROJECT_NAME.firebaseio.com"
});

export const getPrices = functions.https.onRequest((request, response) => {

  const ref = admin.firestore().collection('dates/20191231/prices');
  ref.get().then( snapshot => {
    response.json(snapshot.docs);
  })
  .catch(error => {
    response.status(500).send(error)
  });
});

https://us-central1-PROJECT_NAME.cloudfunctions.net/getPrices にアクセスすると、下記のようなJSONが返ってきます。

[
//省略
    "_fieldsProto": {
      "name": {
        "stringValue": "リーリエ",
        "valueType": "stringValue"
      },
      "id": {
        "stringValue": "smp_397",
        "valueType": "stringValue"
      },
      "price": {
        "integerValue": "69800",
        "valueType": "integerValue"
      },
      "postAt": {
        "timestampValue": {
          "seconds": "1577772000",
          "nanos": 0
        },
        "valueType": "timestampValue"
      }
    },
//省略
]

このままだと使いにくいので、number型は数値で返すようにしたいです。

Firestoreで取得したデータを整えて返す

FirestoreSimple( https://github.com/Kesin11/Firestore-simple )を使うと、より簡単に型付けすることができました。

npm i firestore-simple でインストールします。

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { FirestoreSimple } from 'firestore-simple';

admin.initializeApp({
  credential: admin.credential.applicationDefault(),
  databaseURL: "https://PROJECT_NAME.firebaseio.com"
});

export class Card {
  constructor(
      public id: string,
      public name: string,
      public created: number,
  ) { }
}

export const getPrices2 = functions.https.onRequest(async (request, response) => {
  const firestoreSimple = new FirestoreSimple(admin.firestore())
  const dao = firestoreSimple.collection<Card>({ path: `dates/20191231/prices` })
  const prices = await dao.fetchAll()
  response.send(prices)
});

デプロイします。

export GOOGLE_APPLICATION_CREDENTIALS="/Users/PATH_TO/serviceAccountKey.json"
firebase deploy --only functions

実行すると、下記のようなレスポンスを返します*2

[
  {
    "id": "smp_245",
    "price": 100,
    "name": "イーブイ",
    "postAt": {
      "_seconds": 1577799120,
      "_nanoseconds": 0
    }
  },
  {
    "id": "smp_397",
    "price": 69800,
    "postAt": {
      "_seconds": 1577772000,
      "_nanoseconds": 0
    },
    "name": "リーリエ"
  }
]

Firestoreで取得したデータを並び替えて返す

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { FirestoreSimple } from 'firestore-simple';

admin.initializeApp({
  credential: admin.credential.applicationDefault(),
  databaseURL: "https://PROJECT_NAME.firebaseio.com"
});

export class Card {

  constructor(
      public id: string,
      public name: string,
      public price: number,
  ) { }
}

function compare(a: Card, b: Card) {
  if (a.price < b.price) {
    return 1;
  } else if (b.price > a.price) {
    return -1;
  } else {
    // 価格が同一だった場合、ID順でソートする
    const aId = a.id.toLocaleLowerCase
    const bId = b.id.toLocaleLowerCase
    if (aId === bId) {
      return 0
    }
    return bId < aId ? 1 : -1
  }
}

export const getPrices2 = functions.https.onRequest(async (request, response) => {
  const firestoreSimple = new FirestoreSimple(admin.firestore())
  const dao = firestoreSimple.collection<Card>({ path: 'dates/20191231/prices' })
  const prices = await dao.fetchAll()

  response.send(prices.sort(compare))
});

関連記事

Cloud FunctionsからFirestoreのデータを取得する方法は下記のモノが役に立ちました。

*1:フックという名称で良いのかはあとで調査する

*2:ナノ秒はどういう使い方を想定しているのだろうか?