Firebase Emulator Suiteをつかってみる
こんにちは。
本日はFirebase Emulator Suiteをつかってみたいと思います。
※v9はまだ不安定なため、今回はv8を使用して再現しています。ご注意ください。
1.Firebase 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)などを確認することができます。
この状況で実際に動作確認をしてみたいと思います。
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コレクションにデータがコピーされました。
次に、Functionsの実行ログを確認してみましょう。
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」の場合
regionを切り分ける上での綺麗なやり方が知りたい。。。
それでは。
この記事が気に入ったらサポートをしてみませんか?