LoginSignup
17
9

More than 5 years have passed since last update.

Cloud Functions を TypeScript で書いて Realtime Database のモデル定義をいい感じにする

Posted at

Cloud Functions for Firebase を TypeScript で書いていると、 Firebase Realtime DatabaseCloud Firestore を使っている時にモデルをいい感じに定義することができます。

Cloud Function + TypeScript の環境構築は Cloud Functions を TypeScript で書く - Qiita を参照してください。

具体的にどういうこと

Realtime Database を使っていて、クライアント側は iOS のケースで書いていきます。

iOS

Firebase を使う際に、クライアント側でモデル定義をすると思います。
例えば、 User Model を以下のように定義したとします。

class User: Object {
    @objc dynamic var name: String?
    @objc dynamic var age: Int = 0
}

Realtime Database にはこのように保存されます。

スクリーンショット 2017-11-19 3.48.53.png

* ここでは Salada というライブラリを使っています。
* 詳しくは Firebase Model Framework Saladaを使ってUser Modelを作る - Qiita などを参照してください。

Cloud Functions

any 型

Cloud Functions で User データを取得する時はこのようにしてデータを取得しますが、これだと user が any 型になってしまいます。

const user = await admin.database()
                        .ref('v1/user/-Kxd4TYd_MgjEYhmEO0p')
                        .once('value')
                        .then(snap => snap.val())

model 定義をする

このようなモデル定義を書きます。

declare namespace firebase {
  interface User extends Salada {
    name?: string
    age: number
  }

  interface Salada {
    _createdAt: number
    _updatedAt: number
  }
}

export default firebase

そして user データ取得時に firebase.User にキャストしてあげます。

const user = <firebase.User> await admin.database()
                                        .ref('v1/user/-Kxd4TYd_MgjEYhmEO0p')
                                        .once('value')
                                        .then(snap => snap.val())

こうすると user 型として扱えるため、 name が optional であることも示せます。
また、補完が効くようになり開発効率も上がります。

any 型に対し user.name と書くことも可能ですが、 user.nema としてもエラーにはならないのでやはり型があると安心できますね :relaxed:

もっと便利に使う

const user = <firebase.User> await admin.database()
                                        .ref('v1/user/-Kxd4TYd_MgjEYhmEO0p')
                                        .once('value')
                                        .then(snap => snap.val())

この書き方でも便利でいいのですが、 user オブジェクトに id がないのが不満でした。
-Kxd4TYd_MgjEYhmEO0p が userID なのですが、 admin.database().ref で取得すると id 自体の情報は失われてしまいます。

user._id でアクセスできるようにする

Ref という Util クラスを作ります。 
やることは単純で、 Referenceable を extends している Model であれば取得したデータに _id プロパティをつけているだけです。

ref.ts:

export class Ref {
  static async snapshot<T extends Referenceable>(path: RefPath, id: string): Promise<T> {
    const value: T = await admin.database().ref(`${path}/${id}`).once('value').then(snap => snap.val())
    value._id = id

    return value
  }
}

export enum RefPath {
  User = '/v1/user'
}

export interface Referenceable {
  _id: string
}

firebase.ts:

declare namespace firebase {
  interface User extends Salada, Referenceable {
    name?: string
    age: number
  }

  interface Salada {
    _createdAt: number
    _updatedAt: number
  }
}

この Util クラスを使ってデータを取得すると _id でアクセスできるようになります。

const user = <firebase.User> await Ref.snapshot(RefPath.User, '-Kxd4TYd_MgjEYhmEO0p')
user._id // => -Kxd4TYd_MgjEYhmEO0p

Realtime Database の path も1箇所で定義できて良いですね。

本当は Path は T の Type をみて自動で設定したかったのですが、 TypeScript の Interface は js にすると存在がなくなってしまうようでできませんでした :cry:

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9