Cloud Functions for Firebase を TypeScript で書いていると、 Firebase Realtime Database や Cloud 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 にはこのように保存されます。
* ここでは 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
としてもエラーにはならないのでやはり型があると安心できますね
もっと便利に使う
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 にすると存在がなくなってしまうようでできませんでした