やりたいこと
新人のReact研修用にアプリをつくってもらうのに、APIサーバとDBが必要になったので Firebase をつかって実装することにした。 別の案件もあってほとんど時間が取れないので爆速で実装する必要がある。
実装する
1. Firebase でプロジェクトを作成する
2. Functions の準備
3. Firestore の準備
4. ローカルに作成したプロジェクトと Firebase を連携させる
プロジェクト用のディレクトリを作成。
// package.json 作成 npm init // firebase を扱うツールをインストール npm i -D firebase-tools firebase init
[操作] 矢印カーソルで移動、スペースで選択 Firestore と Functions を選択して Enter
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. ◯ Database: Configure Firebase Realtime Database and deploy rules ◉ Firestore: Deploy rules and create indexes for Firestore ❯◉ Functions: Configure and deploy Cloud Functions ◯ Hosting: Configure and deploy Firebase Hosting sites ◯ Storage: Deploy Cloud Storage security rules ◯ Emulators: Set up local emulators for Firebase features ◯ Remote Config: Get, deploy, and rollback configurations for Remote Config
Use an existing project を選択して Enter
? Please select an option: (Use arrow keys) ❯ Use an existing project Create a new project Add Firebase to an existing Google Cloud Platform project Don't set up a default project
Firebase で作成したプロジェクトを選択して Enter
? Select a default Firebase project for this directory: hoge-app (hoge) hoge-app2 (hoge) hoge-app3 (hoge) ❯ xxxx-xxxxxxxxxx-app (xxxxx-xxxxxxxxx-app) hoge-app4 (hgoe) hoge-app5 (hgoe) hoge-app6 (hoge)
そのままEnter
=== Firestore Setup Firestore Security Rules allow you to define how and when to allow requests. You can keep these rules in your project directory and publish them with firebase deploy. ? What file should be used for Firestore Rules? (firestore.rules)
そのままEnter
Firestore indexes allow you to perform complex queries while maintaining performance that scales with the size of the result set. You can keep index definitions in your project directory and publish them with firebase deploy. ? What file should be used for Firestore indexes? firestore.indexes.json
TypeScript を選んでEnter
=== Functions Setup A functions directory will be created in your project with a Node.js package pre-configured. Functions can be deployed with firebase deploy. ? What language would you like to use to write Cloud Functions? (Use arrow keys) JavaScript ❯ TypeScript
そのまま Enter
? Do you want to use ESLint to catch probable bugs and enforce style? (Y/n)
そのまま Enter
✔ Wrote functions/package.json ✔ Wrote functions/.eslintrc.js ✔ Wrote functions/tsconfig.json ✔ Wrote functions/tsconfig.dev.json ✔ Wrote functions/src/index.ts ✔ Wrote functions/.gitignore ? Do you want to install dependencies with npm now? (Y/n)
Firebase プロジェクトの初期化完了。
5. とりあえずデプロイしてAPIをテストする (エラーが出る)
functions/index.ts
の一部のコメントアウトを外す
import * as functions from "firebase-functions"; // // Start writing Firebase Functions // // https://firebase.google.com/docs/functions/typescript // // ここのコメントアウトを外す export const helloWorld = functions.https.onRequest((request, response) => { functions.logger.info("Hello logs!", {structuredData: true}); response.send("Hello from Firebase!"); });
デプロイ(エラー出る)
# デプロイ(エラー出る) $ firebase deploy --only functions
エラー内容
/Users/yanagi/dev/blog/test-functions/functions/.eslintrc.js 13:44 error Missing trailing comma comma-dangle
functions/.eslintrc.js
の13行目にカンマがないよと言われているので、カンマをつける。
カンマをつけたらもう一度デプロイ
# デプロイ $ firebase deploy --only functions
今度はうまくいく。
デプロイが完了したらコンソールからAPIを叩いてみる。Hello from Firebase!
が返ってくればOK。
URLはFirebaseのプロジェクトのFunctionsにで見れる。(他にも確認のしかたあるかもしれん)
# リクエスト
$ curl https://hgoehgoe.cloudfunctions.net/helloWorld
Hello from Firebase!
6. Express でAPIを作る
express
で実装するAPIと functions.https.onRequest
で実装されているいまのAPIを削除するAPIが混在するとなぜかcorsエラーが消えないという呪いにかかったので、とりあえず最初から書かれている functions.https.onRequest
で実装されているAPIを削除しておく。
// functions/index.ts import * as functions from "firebase-functions"; // // Start writing Firebase Functions // // https://firebase.google.com/docs/functions/typescript // // ここ以下を削除しておく export const helloWorld = functions.https.onRequest((request, response) => { functions.logger.info("Hello logs!", {structuredData: true}); response.send("Hello from Firebase!"); });
express を install
$ cd functions $ npm i express $ npm i -D @types/express
APIを実装
import * as functions from "firebase-functions"; import * as express from "express"; const app = express(); app.get("/hello", (req, res) => { res.send("hello express API"); }); const api = functions.https.onRequest(app); module.exports = {api};
デプロイしてテスト (hello express API が返ってくればOK)
# リクエスト
$ curl curl https://hgoehgoe.cloudfunctions.net/api/hello
hello express API
とりあえずAPIは完成。
7. cors対応をしておく
基本的にブラウザからAPIを叩くことになると思うのでcors対応をしておく
現状のままブラウザからAPIを叩くと、下記のようなcorsエラーが返ってくる
Access to fetch at 'https://hogehoge.cloudfunctions.net/api/hello' from origin 'https://console.firebase.google.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
この cors エラーを回避する処理を実装する
cors をインストール
$ npm i cors
corsエラー回避処理を実装
import * as functions from "firebase-functions"; import * as express from "express"; import * as cors from "cors"; const app = express(); // cors対策 app.use(cors({origin: true})); app.get("/hello", (req, res) => { res.send("hello express API"); }); const api = functions.https.onRequest(app); module.exports = {api};
これでブラウザからAPIを叩いてもcorsエラーが起きなくなる。
8. Firestore をつかってCRUDを実装する
firebase-admin をインストール
$ npm i firebase-admin
まずはデータがなくては始まらないので、Firestoreにデータを登録できるようにする。
今回は Firebase の members というコレクションにデータを登録することにする。
import * as functions from "firebase-functions"; import * as express from "express"; import * as cors from "cors"; import * as admin from "firebase-admin"; const app = express(); // cors対策 app.use(cors({origin: true})); // リクエストデータ取得のためのパーサ登録 app.use(express.json()); // firebase-admin初期化 admin.initializeApp(); app.post("/members", async (req, res) => { type RequestData = { name: string, message: string, }; // リクエストのデータを取得 const requestData: RequestData = req.body; // Firestoreのmembersコレクションにデータを保存 await admin .firestore() .collection("members") .doc() .set(requestData); res.send({message: "success"}); }); const api = functions.https.onRequest(app); module.exports = {api};
追加部分を説明していく
リクエストで送られてくるjsonデータをオブジェトにパースしてくれるパーサを登録している
// リクエストデータ取得のためのパーサ登録 app.use(express.json());
firebase-adminの初期化処理。これをやっておかないと Firestore に接続できない。
// firebase-admin初期化 admin.initializeApp();
リクエストされたデータの取得。 パーサを登録しているので、オブジェクトで取得できる。
// リクエストのデータを取得 const requestData: RequestData = req.body;
メインの Firestore にデータを登録する処理。 めちゃめちゃかんたんに書けるので Firestore 大好きである。
// Firestoreのmembersコレクションにデータを保存 await admin .firestore() .collection("members") .doc() .set(requestData); res.send({message: "success"});
エラーのハンドリング処理は入れていないので、自分で入れて下さい!
デプロイしてテスト ({"message":"success"}がかえってくればOK)
# リクエスト $ curl -X POST -H "Content-Type: application/json" -d '{"name":"ディオ", "message":"おまえは今まで食ったパンの枚数をおぼえ ているのか?"}' https://hogehoge.cloudfunctions.net/api/members # {"message":"success"}がかえってくればOK {"message":"success"}
{"message":"success"}
が返ってきたら Firebase のコンソールから Firestore のコンソールを開いてデータを確認する。
ちゃんとデータがはいっていればOK。
9. Firebase からデータを読み取る処理を書く
import * as functions from "firebase-functions"; import * as express from "express"; import * as cors from "cors"; import * as admin from "firebase-admin"; const app = express(); // cors対策 app.use(cors({origin: true})); // リクエストデータ取得のためのパーサ登録 app.use(express.json()); // firebase-admin初期化 admin.initializeApp(); // members取得API app.get("/members", async (req, res) => { // Firebaseからデータ取得 const members = await admin .firestore() .collection("members") .get() .then(async (snapshot) => await snapshot.docs.map((v) => v.data())); res.send({members}); }); // members登録API app.post("/members", async (req, res) => { type RequestData = { name: string, message: string, }; // リクエストのデータを取得 const requestData: RequestData = req.body; // Firestoreのmembersコレクションにデータを保存 await admin .firestore() .collection("members") .doc() .set(requestData); res.send({message: "success"}); }); const api = functions.https.onRequest(app); module.exports = {api};
追加部分の説明
membersデータ取得のAPI。Firestore からデータ取得する処理書いただけ。
// members取得API app.get("/members", async (req, res) => { // Firebaseからデータ取得 const members = await admin .firestore() .collection("members") .get() .then(async (snapshot) => await snapshot.docs.map((v) => v.data())); res.send({members}); });
デプロイしてテスト (さっき登録したデータが返ってきたらOK)
# リクエスト curl https://hgoehgoe.cloudfunctions.net/api/members # さっき登録したデータが返ってきたらOK {"members":[{"message":"おまえは今まで食ったパンの枚数をおぼえているのか?","name":"ディオ"}]}
PUT と DELETE はつかれたからまた今度
おまけ
[VSCode使ってるひとだけ] functions/index.ts
の一行目の import で ESLint のエラーが出てると思うのでそれを解消する
この記事を読んでくれ。
おわり