![見出し画像](https://assets.st-note.com/production/uploads/images/71166527/rectangle_large_type_2_3cff4b53a3c26cd56254f5992ff41d8e.jpg?width=1200)
Firebase Emulator Suiteをつかってみる
こんにちは。
本日はFirebase Emulator Suiteをつかってみたいと思います。
※v9はまだ不安定なため、今回はv8を使用して再現しています。ご注意ください。
1.Firebase Emulator Suiteとは?
Firebase Local Emulator Suite は、Cloud Firestore、Realtime Database、Cloud Storage、Authentication、Cloud Functions、Pub/Sub、Firebase Hosting を使用してアプリをローカルでビルドおよびテストするデベロッパー向けの高度なツールセットです。機能が豊富なユーザー インターフェースを備えており、アプリの本稼働やプロトタイピングにかかる時間を短縮できます。
Local Emulator Suite を使用したローカル開発は、プロトタイピング、開発、継続的インテグレーションのワークフローに適しています。
Firebaseは、Emulatorが導入されるまではローカル開発環境がなく、都度Firebase上に開発環境用のプロジェクトを構築する必要がありました(手間がかかりました)。
しかし、Emulatorの登場により、FirestoreやFunctionsなどを各自の開発環境で持つことができるようになりました。
それでは早速始めていきましょう。
2初期設定をする
// Firebase自体の初期化
$ firebase init
// Emulator Suiteの初期化
$ firebase init emulators
対話形式で初期化を進めていきましょう。
Emulatorで使用する要素に関して質問がありますので、選択して設定を完了へと進んでいきます。
設定が完了すると、各ファイルが生成されます。
// firebase.json
{
"firestore": {
"rules": "firestore.rules.json",
"indexes": "firestore.indexes.json"
},
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
},
"emulators": {
"auth": {
"port": 9099,
"host": "localhost"
},
"firestore": {
"port": 8080,
"host": "localhost"
},
"functions": {
"port": 5051,
"host": "localhost"
},
"ui": {
"enabled": true, // trueにすることでlocalhost:4000でEmulateのページをひらくことができます
"port": 4000,
"host": "localhost"
}
}
}
このままでも起動はできますが、実際のコード上から各Emulatorのポートへ接続して使用できるように設定していきます。
3.Emulatorに繋いでみる
では、実際に接続してみましょう。
// index.ts
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import 'firebase/compat/functions';
//----------------------------------------
// config
//----------------------------------------
const config = {
apiKey: process.env.NEXT_PUBLIC_DEV_API_KEY,
authDomain: process.env.NEXT_PUBLIC_DEV_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_DEV_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_DEV_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_DEV_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_DEV_APP_ID
};
//----------------------------------------
// initialize firebase
//----------------------------------------
if (!firebase.apps.length) {
firebase.initializeApp(process.env.NODE_ENV === 'development' ? configDev : configProd);
}
const isEmulator = window.location.hostname === 'localhost'
const db = firebase.firestore();
const functions = firebase.app().functions(isEmulator ? 'us-central1' : 'asia-northeast1'); // us-central1じゃないとcallable functionが動かない
const auth = firebase.auth();
// localhostの場合はEmulatorにつなぐ
if(isEmulator) {
db.useEmulator("localhost", 8080);
functions.useEmulator("localhost", 5051);
auth.useEmulator('http://localhost:9099/');
}
export { db, functions, auth };
Emulatorを起動している状態かつlocalhostの場合は、Emulatorに接続することができます。
それでは、早速Emulatorを起動してみましょう。
$ firebase emulators:start
Emulatorの画面には、localhost:4000にアクセスすることで、AuthenticationやFirestore、Logs(Functions)などを確認することができます。
![画像1](https://assets.st-note.com/production/uploads/images/71085586/picture_pc_ac4452190f9e3d3a4283ec0faed489d9.png?width=1200)
この状況で実際に動作確認をしてみたいと思います。
● フロントの画面からボタンをクリックしてデータをFirestoreに保存します。
● 保存したFirestoreデータをトリガーにして、別のコレクションにデータをコピーします。
● フロントの画面からボタンをクリックして、Callable Functionを起動します。
● Callable FunctionからFirestoreにデータを保存します。
4.フロントの画面からボタンをクリックしてデータをFirestoreに保存。
// pages/index.tsx
const Home: NextPage = () => {
const onSaveHandler = async () => {
const id = 'a7d5dd1e-1f7d-47e7-8be2-314150e5676f';
const datas = {
id: id,
name: 'yamada taro',
age: 38,
email: 'example@example.com'
};
await db.doc(`users/${id}`).set(datas);
};
return (
<div>
<h1>サンプル</h1>
<button onClick={() => onSaveHandler()}>
保存します
</button>
</div>
);
};
export default Home;
5.保存したFirestoreデータをトリガーにしてデータを別コレクションへコピー。
// functions/sample.tsx
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
const firestore = admin.firestore();
module.exports = functions.region("asia-northeast1").firestore.document('users/{userId}').onWrite(async (change, context) => {
console.log(context.params.userId);
const userId = context.params.userId as string;
await firestore.doc(`users_copy/${userId}`).set({
id: userId,
name: context.params.name,
age: context.params.age,
email: context.params.email
});
});
画面からボタンをクリックしました。
その結果、usersコレクションにデータが投入され、データの投入がトリガーとなり、users_copyコレクションにデータがコピーされました。
![スクリーンショット 2022-01-30 22.03.45](https://assets.st-note.com/production/uploads/images/71085919/picture_pc_27c4849b1f80be2a5e7da68f0ceb4738.png?width=1200)
![スクリーンショット 2022-01-30 22.04.01](https://assets.st-note.com/production/uploads/images/71085924/picture_pc_80476ecbfa2bf9183fc284f0340f9f82.png?width=1200)
次に、Functionsの実行ログを確認してみましょう。
![画像4](https://assets.st-note.com/production/uploads/images/71086120/picture_pc_1db0a2412abcf5cb67a5baca54239183.png?width=1200)
idがログに表示されていますね。
これでEmulatorを使用してローカル開発環境を構築することができました。
次に、Emulator上でCallable Functionを実行してみましょう。
6.フロントの画面からボタンをクリックしてCallable Functionを起動。
// pages/index.tsx
const Home: NextPage = () => {
const onSaveHandler = async () => {
const id = 'a7d5dd1e-1f7d-47e7-8be2-314150e5676f';
const datas = {
id: id,
name: 'yamada taro',
age: 38,
email: 'example@example.com'
};
await db.doc(`users/${id}`).set(datas);
};
// ここから追加
const onCallSaveHandler = async () => {
const onCallSample = functions.httpsCallable(isMockFunctionsPath('sample'));
await onCallSample({ data: {} }).catch((e) => {
console.log(e);
alert(e);
});
};
// ここまで追加
return (
<div>
<h1>サンプル</h1>
<button onClick={() => onSaveHandler()}>
保存します
</button>
{ /* ここから追加 */}
<button style={{ backgroundColor: 'blue' }} onClick={() => onCallSaveHandler()}>
onCall発火
</button>
{ /* ここまで追加 */}
</div>
);
};
export default Home;
// util/index.ts
//----------------------------------------
// localhostの場合mock関数の文字列を付与して返す
//----------------------------------------
export const isMockFunctionsPath = (path: string) => {
const isEmulator = window.location.hostname === 'localhost';
if(isEmulator) {
return `mock${path}`;
}
return path;
}
フロント画面に新たにonCallイベントでFunctionsを起動するイベントを設定しています。
7.Callable FunctionからFirestoreにデータを保存。
// functions/mock/call.tsx
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
const firestore = admin.firestore();
module.exports = functions.region('us-central1').https.onCall((data, context) => {
console.log(context);
console.log(data);
firestore
.doc('mock/123456789')
.set({
id: '123456789',
body: 'onCallイベントです'
})
.catch((e) => {
console.log(e);
});
return 'hello onCall';
});
onCallしているCallable Functionsですが、Functions内からmockコレクションにデータを保存しているだけのFunctionsです。
7-1.isMockFunctionsPath()関数
localhostの場合、エミュレータでonCallを実行するためには、regionが`us-central1`である必要があります。
そのため、mock用の関数を作成し、isMockFunctionsPath()関数でmock用の関数を呼び出すために、関数名に'mock'という文字列を追加しています。
これにより、コード上でmock用の関数を呼び出すことができます。
7-2.Callable Functionsが「call」の場合
● localhostの場合のFunctions名 → mockcall
● localhost以外の場合のFunctions名 → call
regionを切り分ける上での綺麗なやり方が知りたい。。。
それでは。