Cloud Functions for Firebaseはいわゆる AWS Lambdaの Firebase 版です。サーバレスで、サーバ側にロジックを構築できるわけですが、FirebaseやFirestoreが便利すぎる件にくわえて、さらにそれらの処理をトリガーにロジックがうごかせるわけなので、、ちゃんと勉強しておいた方がよさそうです。。
ちなみに、サイトにあるユースケースを見ると
- Realtime Database の書き込みをトリガーに、ユーザに通知を投げるとか、
- 画像のアップロードをトリガーに、画像の加工を行うとか
- GitHub webhook APIをつかって、commitのpushをトリガーにSlackに通知を投げる
なんて事ができそうです。HTTPSの受信をトリガーに出来るので、簡単なRESTサーバとかも構築できますね。。
という事で作業メモ。
やってみる
Hello World
firebase-toolsのインストール
$ npm install -g firebase-tools
$ source ~/.bash_profile
$ firebase --version
6.3.0
プロジェクトのディレクトリ作成
$ mkdir fb_function_samples && cd $_
Firebaseにログインしておく
$ firebase login
? Allow Firebase to collect anonymous CLI usage and error reporting information?
No
Visit this URL on any device to log in:
https://accounts.google.com/o/oauth2/auth?client_id=... // OAuthでアクセスの認可まち
Waiting for authentication...
✔ Success! Logged in as masatomix@example.com
プロジェクト作成
$ firebase init functions
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########
You're about to initialize a Firebase project in this directory:
質問には基本デフォルトでよさげ
? Select a default Firebase project for this directory: [create a new project]
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
? Do you want to install dependencies with npm now? Yes
✔ Firebase initialization complete!
Project creation is only available from the Firebase Console
Please visit https://console.firebase.google.com to create a new project, then run firebase use --add
いわれるままに、コマンドを実行(どのFirebase上のプロジェクトにデプロイする?とかを指定してるぽい)
$ firebase use --add
? Which project do you want to add? sandbox-xxxxxxx
? What alias do you want to use for this project? (e.g. staging) test_env
Created alias test_env for sandbox-xxxxxxx.
Now using alias test_env (sandbox-xxxxxxx)
結果、プロジェクト構成はこんな感じ
$ pwd
/..../fb_function_samples
$ tree -a
.
├── .firebaserc
├── .gitignore
├── firebase.json
└── functions
├── .gitignore
├── index.js
├── node_modules/.... 割愛
├── package-lock.json
└── package.json
さあ index.jsをいじってみます。
$ cat functions/index.js
const functions = require('firebase-functions');
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
});
exports.echo = functions.https.onRequest((request, response) => {
const task = request.body
console.log(JSON.stringify(task))
// const firestore = admin.firestore()
// const ref = firestore.collection("todos")
response.send(JSON.stringify(task));
});
HTTPのリクエストの受信をトリガーに動く、メソッド helloworldとechoを作成しました。
サーバへデプロイ
$ firebase deploy --only functions
=== Deploying to 'sandbox-xxxxxxx'...
i deploying functions
i functions: ensuring necessary APIs are enabled...
✔ functions: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (42.39 KB) for uploading
✔ functions: functions folder uploaded successfully
i functions: creating Node.js 6 function helloWorld(us-central1)...
i functions: creating Node.js 6 function echo(us-central1)...
✔ functions[echo(us-central1)]: Successful create operation.
Function URL (echo): https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/echo
✔ functions[helloWorld(us-central1)]: Successful create operation.
Function URL (helloWorld): https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/helloWorld
✔ Deploy complete!
Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/sandbox-xxxxxxx/overview
さあ、デプロイが完了したので、 firebase loginしたコンソールで、curlで呼び出してみます
$ curl https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/helloWorld
Hello from Firebase!
$ cat request.json
{
"id": "001",
"name": "こんにちは",
"isDone": true
}
$ curl -X POST -H "Content-Type:application/json" \
--data-binary @request.json \
https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/echo | jq
{
"id": "001",
"name": "こんにちは",
"isDone": true
}
$
よさそうです。
requireでなくてimportをつかいたい
vue-cliで vue.jsのアプリを書いていると、ES2015形式(?)でコードを書いてますよね。外部モジュールをconst functions = require('firebase-functions');
ではなく import * as functions from 'firebase-functions'
って書きたい、ってヤツです。
ってことで、babelを入れてトランスパイルすることで、ES2015形式でかけるようにしてみます。
babel インストール他
$ cd functions
$ npm install --save-dev babel-cli babel-preset-es2015
$ cd ../
firebase.json にpredeployを追加(ココに書いておくと、deploy前に実行されます)
$ cat firebase.json
{
"functions": {
"predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" // buildはこのあと定義します
}
}
package.json にbuildのスクリプトの定義を追加
$ cat functions/package.json
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"build": "mkdir -p dist && ./node_modules/.bin/babel ./*.js --out-file ./dist/index.js", // buildの定義を追加
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"main": "dist/index.js", // デプロイするファイルをココで指定
"engines": {
"node": "8" // ついでにnodeを8にしてみる
},
"dependencies": {
"firebase-admin": "~6.0.0",
"firebase-functions": "^2.1.0"
},
"private": true,
"devDependencies": { // これらは自動で追加されたはず
"babel-cli": "^6.26.0",
"babel-preset-es2015": "^6.24.1"
}
}
babel設定ファイル作成
$ cat functions/.babelrc
{
"presets": [
[
"es2015",
{
"targets": {
"node": "current"
}
}
]
]
}
ディレクトリ構成は以下のように。
$ tree -a
.
├── .firebaserc
├── .gitignore
├── firebase.json
└── functions
├── .babelrc
├── .gitignore
├── index.js
├── node_modules/.... 割愛
├── package-lock.json
└── package.json
デプロイ
$ firebase deploy --only functions
=== Deploying to 'sandbox-xxxxxxx'...
i deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run build
> functions@ build /..../fb_function_samples/functions
> mkdir -p dist && ./node_modules/.bin/babel ./*.js --out-file ./dist/index.js
✔ functions: Finished running predeploy script.
i functions: ensuring necessary APIs are enabled...
✔ functions: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (55.53 KB) for uploading
✔ functions: functions folder uploaded successfully
i functions: updating Node.js 8 function helloWorld(us-central1)...
i functions: updating Node.js 8 function echo(us-central1)...
✔ functions[helloWorld(us-central1)]: Successful update operation.
✔ functions[echo(us-central1)]: Successful update operation.
✔ Deploy complete!
Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/sandbox-xxxxxxx/overview
$
ログを見ると、babelのトランスパイルが動いてからデプロイされていますね。。
curlで実行出来ることを確認してください。
ちゃんとES2015の文法に書き直して、再ラン
さっきはindex.jsをもとのファイルそのままつかいましたが、以下のようにトランスパイルが必要なコードにしてみます。
import * as functions from 'firebase-functions'
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
export const helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!")
})
export const echo = functions.https.onRequest((request, response) => {
const task = request.body
console.log(JSON.stringify(task))
// const firestore = admin.firestore()
// const ref = firestore.collection("todos")
response.send(JSON.stringify(task))
})
も一度デプロイしてみましょう。ちなみに dist/index.js をみても、ちゃんと変換されてそうですね。。
っていうかTypeScriptつかえば?ってことかな
いまいち JavaScriptのES2015とTypeScriptの関係がよく分かってないのですが、ES2015でbabelで前提でコードを書くのと、Gradual Typing なTypeScript で書くのって、構文おなじっぽいですよね?ってことで、そもそも初めのプロジェクト作成で、JavaScriptでなくてTypeScriptを選べばいいんじゃんね?っておもいました。。
ということで、別のTypeScriptプロジェクトを作成します。index.jsは後でつかうのでどっかに取っておきましょう。
TypeScriptをつかってみる
プロジェクト作成(こんどはTypeScriptをえらびますよ)
$ mkdir fb_function_samples && cd $_
$ firebase init functions
? Select a default Firebase project for this directory: [create a new project]
? What language would you like to use to write Cloud Functions? TypeScript ← TypeScriptをえらぶ
? Do you want to use TSLint to catch probable bugs and enforce style? No ← いったんNo
? Do you want to install dependencies with npm now? Yes
✔ Firebase initialization complete!
Project creation is only available from the Firebase Console
Please visit https://console.firebase.google.com to create a new project, then run firebase use --add
$ firebase use --add
? Which project do you want to add? sandbox-xxxxxxx
? What alias do you want to use for this project? (e.g. staging) test_env
Created alias test_env for sandbox-xxxxxxx.
Now using alias test_env (sandbox-xxxxxxx)
$
ディレクトリ構成
$ tree -a
.
├── .firebaserc
├── .gitignore
├── firebase.json
└── functions
├── .gitignore
├── node_modules/.... 割愛
├── package-lock.json
├── package.json
├── src
│ └── index.ts
└── tsconfig.json
さて、待避しておいた functions/index.js の内容を functions/src/index.ts へ上書きして、firebase deploy します
$ firebase deploy --only functions
=== Deploying to 'sandbox-xxxxxxx'...
i deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run build
> functions@ build /..../fb_function_samples_ts/functions
> tsc
✔ functions: Finished running predeploy script.
i functions: ensuring necessary APIs are enabled...
✔ functions: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (43.74 KB) for uploading
✔ functions: functions folder uploaded successfully
i functions: updating Node.js 8 function helloWorld(us-central1)...
i functions: updating Node.js 8 function echo(us-central1)...
✔ functions[helloWorld(us-central1)]: Successful update operation.
✔ functions[echo(us-central1)]: Successful update operation.
✔ Deploy complete!
Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/sandbox-xxxxxxx/overview
さきほどのbabelではpredeployにbabelの処理を手動で追加しましたが、TypeScriptで作成したプロジェクトにはあらかじめ tsc(TypeScriptのトランスパイラ?)が事前に動くように設定されていますね。
(っていうかさっきbabelの処理を入れる際に、TypeScriptのプロジェクトを参考にしたので当たり前なのですが :-))
ちなみにトランスパイルするときのチェック処理などは、functions/tsconfig.json の定義に則って行われるみたいなので、必要に応じて設定ファイルを見直してください。
参考:
http://js.studio-kingdom.com/typescript/project_configuration/tsconfig_json
さて、curlでやってみて、おなじ結果が得られることを確認しましょう。
$ curl https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/helloWorld
Hello from Firebase!
$ cat request.json
{
"id": "001",
"name": "こんにちは",
"isDone": true
}
$ curl -X POST -H "Content-Type:application/json" \
--data-binary @request.json \
https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/echo | jq
{
"id": "001",
"name": "こんにちは",
"isDone": true
}
$
なんだか遠回りした気がしますが、Vue.jsでつかってるJavaScriptとおなじ文法で、Functionsを記述することができるようになりました!
おつかれさまでした。