(2)CloudFunctions for Firebaseに触れる
概要
前回まででフロントエンドのReactアプリとfirebaseの設定とfunctionsの雛形の作成が完了しました。
前回:https://qiita.com/sugimo-ne/items/c363342a0d83c7ab82b4
今回はfunctionsをどのように作成して動作させるのか、簡単にRESTAPIの作成とCloud Firestoreとの連携もしてみたいと思います。
functionsに触れる。
こちらの公式ドキュメントに沿ってサンプルのコードを動かしていきます。
firebase functionsエンドポイント作成とレスポンスの設定をしてみる。
まずその前に、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) => {
// functions.logger.info("Hello logs!", {structuredData: true});
// response.send("Hello from Firebase!");
// });
自動で雛形が作成されています。
firebase emulatorが起動されていない場合、
$ firebase emulators:start
で起動させ、
index.jsのコメントアウトを外してみてください。
コンソールにfunctionのローカルのエンドポイントが出力されるかとおもます。
*スクショ
http://localhost:5001/プロジェクト名/us-central1/helloWorld
こちらのエンドポイントブラウザからアクセスしてみましょう。
functions/index.jsの
exports.helloWorld = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", {structuredData: true});
response.send("Hello from Firebase!");
});
helloWorldが実行されてresponse.sendで指定した文字列がリクエストに対するレスポンスとして帰ってきています。
試しに
exports.helloWorld
こちらの記述を
exports.sample
と書き換え、
response.send("Hello from Firebase!");
を
response.send("yaa");
と書き換えてみます。
先ほどと変わって
http://localhost:5001/プロジェクト名/us-central1/sample
こちらにアクセスするとyaaという文字列が返却されているのがわかるかと思います。
(request, response)を引数に取り、responseのメソッドを使用したものを関数を与え、
返り値を変数に格納し、exportすると変数名に対応したアクセスがあった際に実行されるためresponseの内容が返ってくるようになります。
公式のサンプルを動かす。
次にcloudfunctionsでfirebaseの提供するサービスと連携するscriptを見てみます。
公式のサンプルに従ってローカルで起動しているcloudfirestoreと連携したscriptを動かしてみます。
functions/index.jsの中身をサンプルに入れ替えます。
const functions = require('firebase-functions');
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
.onCreate((snap, context) => {
const original = snap.data().original;
console.log('Uppercasing', context.params.documentId, original);
const uppercase = original.toUpperCase();
return snap.ref.set({uppercase}, {merge: true});
});
中身を見ていくと
functions.firestore.document()
の引数にfirestoreのcollectionのドキュメントまでのパスを与え、.onCreate
関数でドキュメントが作成された際の処理を記述しています。
ここではmessagesコレクションに作成されたドキュメントのoriginalフィールドの値を取得し、すべて英数字の大文字に変換し同じドキュメント内のussercaseフィールドに保存する処理が記述されています。
firebase emulatorにアクセスし、firestoreのタブを開きます。
[start collection]ボタンを押して、
DocumentIDをデフォルト値
Fieldをoriginal
としてsaveします。
そうするとローカルのEmulatorでデータベースの値が更新されているのが確認できます。
functionsにexpressを使用してREST APIを作成し、データをローカルのfirestoreに保存する。
これまでやった内容をもとに
RESTAPIを作成してfirestoreのデータを追加してみましょう。
$ cd functions
$ npm install --save-dev firebase-admin express cors
必要なパッケージ類をインストールします。
ソースコード
functions/admin.js
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
module.exports = {
db,
};
admin.jsにfirebase-admin(firebaseをサーバサイドで利用するためのsdk)を使うための初期設定を行なっています。
ローカルのemulatorに対して操作を行う際にはinitializeApp()
の引数は与えなくて大丈夫です。
controllerで使用するdbもインスタンス化してexportします。
functions/controllers/TestController.js
const {db} = require("../admin");
const messagePost = async (req, res) => {
try{
console.log(req.body)
const {text} = req.body;
const ref = db.collection("messages");
const docRef = await ref.add({
original: text
})
const docSnapshot = await docRef.get();
const newMessage = {
id: docSnapshot.id,
...docSnapshot.data(),
};
res.json({
message: "success",
data:newMessage
})
}catch(e){
console.log(e)
res.json({
message: "failed",
error:e
})
}
}
module.exports = {
messagePost
}
messagePost関数はexpressのrouterでルーティングにマッチしたpostリクエストを受け取った際に発火させます。
リクエストボディのtextを受け取ってadmin sdkを使用してfirestoreにデータを保存する処理を記述しています。
これによってtextデータを含めたリクエストがきた際に、
messagesコレクションに新しく、
original: textの中身
のフィールドを持ったドキュメントが追加されるようになります。
functions/routes.js
const {Router} = require("express");
const router = new Router();
const {messagePost} = require("./controllers/TestController");
router.route("/test").post(messagePost);
module.exports = router;
routes.jsではexpressのルーティング機能を利用するための設定を行なっています。
controllerのmessagePost関数を読み込んで
/testに対してpostリクエストがあった際にmessagePostを呼び出します。
functions/index.js
const functions = require("firebase-functions");
const express = require("express");
const cors = require("cors");
const app = express();
app.use(cors({origin: true}));
const router = require("./routes");
app.use("/", router);
exports.api = functions.https.onRequest(app);
exports.makeUppercase = functions.firestore.document("/messages/{documentId}")
.onCreate((snap, context) => {
const original = snap.data().original;
console.log("Uppercasing", context.params.documentId, original);
const uppercase = original.toUpperCase();
return snap.ref.set({uppercase}, {merge: true});
});
index.jsで
- expressの初期化
- corsの設定
- ミドルウェアの設定(routes.jsからrouterを読み込みmidlleware化する。)
- apiモジュールのexport
を追加しました。
これによって/api/*にhttpリクエストがきた際に、
expressのrouterがルーティングにマッチした処理を行います。
ソースコードの編集は以上です。
動作確認
動作確認にはPOSTMANを利用して、
bodyにtextを含めて作成したAPIに対してリクエストを送信します。
すると正しく処理ができた場合にレスポンスとして作成されたデータのがjson形式でレスポンスとして返ってきていることがわかります。
Emulatorで確認しみましょう。
作成したAPIを呼び出してデータが保存されていることと、
公式のサンプルコードの処理が走ってuppercaseフィールドに、大文字に変換された値が保存されているのがわかります。
デプロイしてみる。
実際にデプロイして動作確認を行います、見てデプロイの仕方を覚えるだけでも大丈夫ですが興味のある方は、従量課金の設定が必須となるので任意で進めてみてください。
firebaseコンソールでsparkプランとなっている場合firebaseの関数はデプロイできないので従量課金プランに変更します。
その後/functionsより一つ上の階層で
$ firebase deploy --only functions
とコマンドを実行します。
するとcliでデプロイ処理が行われます。
lintの設定で引っかかるとデプロイできないので引っかかった方はコードを修正したりeslint.rcの内容を編集するなどしてください。
こちらが出たら成功です。
firebaseのコンソールを開き、
Functionsを開きます。
二つの関数が作成されていて
api関数のトリガーの部分のリクエストにurlが貼ってあるのでそのurlをコピーします。
その後、postmanのエンドポイントをコピーしたurlに変えてpostしてみます。
レスポンスが正常に返ってきたらfirebaseのコンソールを確認します。
ローカルのEmulatorではなく本番のfirebaseにデータが保存されているのがわかります。
まとめ
ここまででfirebase上でAPIを自作し、firestoreにデータを保存させたり、
firestoreにデータが保存されるのをトリガーに処理を走らせることができるようになりました。
フロントエンドでアプリを作って連携させたり、応用すればそれなりに動くものが作れるようになるはずです。
次回からはいよいよ今回作るタスク管理アプリの概要から実装まで順を追って行なっていきたいと思います。