[Firestore]チュートリアルを試してみた（Node編）

## 概要

- Firebase公式サイトにある[Cloud Firestore データベースを作成する](https://firebase.google.com/docs/firestore/quickstart?hl=ja)を試してみた記録です
- NodeからFirestoreへの書き込み/読み取りを実装してみます
- Firestoreを操作する処理をExpressを使ってAPIとして公開するところまで紹介します

## Firestoreのデータベースを作成する

- [Firebaseのコンソール](https://console.firebase.google.com/)で操作していきます
- ログインしたら「プロジェクトを追加」からプロジェクトを作成します
<img width="911" alt="スクリーンショット 2019-09-19 23.18.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/56899f86-f5b7-afa0-5b78-c6affa22c6d9.png">
<img width="487" alt="スクリーンショット 2019-09-19 23.21.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/635c6786-2530-57c4-6295-a25f16f892d0.png">
<img width="686" alt="スクリーンショット 2019-09-19 23.22.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/f18d9b89-c29e-0f6d-773b-2341d75906e9.png">

- プロジェクトの作成には少し時間がかかります
- 作成できたらサイドメニューの「データベース」を選択し「データベースを作成」から作成していきます
<img width="608" alt="スクリーンショット 2019-09-19 23.23.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/c095aef7-1542-d843-902e-9883bbb050cb.png">

- NodeでFirestoreにアクセスする場合はテストモードとロックモードどちらでも大丈夫です
- Webからはテストモードでないとアクセスできないので検証の用途で使いまわしたい場合はテストモードにしておいてもいいかも

<img width="643" alt="スクリーンショット 2019-09-19 23.26.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/ebdb75a0-5083-768d-dbe7-6bff51daffee.png">

- 作成が完了したら準備OKです
<img width="895" alt="スクリーンショット 2019-09-19 23.29.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/c517063b-c14d-503f-bcae-2cd178eb0490.png">

## サービスアカウントの秘密鍵の取得

- Firestoreにアクセスするための秘密鍵を取得しておきます
    - 歯車から「プロジェクトの設定」->「サービスアカウント」->「新しい秘密鍵の生成」
    - ファイル名は何でもいいですがここでは`ServiceAccount.json`で保存しておきます
<img width="833" alt="スクリーンショット 2019-09-19 23.37.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/283127e3-f95b-42af-4d17-8b14d5df9b90.png">

## NodeからFirestoreにアクセスする

- 適当なディレクトリを作成しセットアップ

```sh
mkdir firestore-sample-node
cd firestore-sample-node
npm init -y
```

- Firebase関連のSDKをインストールする

```sh
npm i firebase-admin firebase-functions
```

- jsファイルの作成
    - index.jsを作成し以下の内容を記述
    - この例では`ServiceAccount.json`をfirestore-sample-nodeフォルダ直下においている

```js:index.js
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const ServiceAccount = require('./ServiceAccount.json');

admin.initializeApp({ credential: admin.credential.cert(ServiceAccount) });

const db = admin.firestore();
const docRef = db.collection('users').doc('alovelace');

const setAda = docRef.set({
  first: 'Ada',
  last: 'Lovelace',
  born: 1815,
});
```

- 実行してみる

```sh
node index.js
```

- Firestoreのコンソールを見てみるとデータが追加されています
<img width="634" alt="スクリーンショット 2019-09-19 23.51.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/37a74804-69da-70a9-4676-c167b95d7214.png">

- ※RDBの感覚で言うとCollectionはテーブル、Documentはレコード
![](https://firebase.google.com/docs/firestore/images/structure-data.png?hl=ja)

- 更にデータを追加してみる

```js:index.js
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const ServiceAccount = require('./ServiceAccount.json');

admin.initializeApp({ credential: admin.credential.cert(ServiceAccount) });

const db = admin.firestore();
const docRef = db.collection('users').doc('alovelace');

const setAda = docRef.set({
  first: 'Ada',
  last: 'Lovelace',
  born: 1815,
});

const aTuringRef = db.collection('users').doc('aturing');

const setAlan = aTuringRef.set({
  first: 'Alan',
  middle: 'Mathison',
  last: 'Turing',
  born: 1912,
});
```

- 実行する

```sh
node index.js
```
<img width="629" alt="スクリーンショット 2019-09-19 23.56.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/968d33f7-2a07-e082-573f-db6bd38012e5.png">

- 次は登録したデータを取得してみる

```js:index.js
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const ServiceAccount = require('./ServiceAccount.json');

admin.initializeApp({ credential: admin.credential.cert(ServiceAccount) });

const db = admin.firestore();
const docRef = db.collection('users').doc('alovelace');

db.collection('users')
  .get()
  .then(snapshot => {
    snapshot.forEach(doc => {
      console.log(doc.id, '=>', doc.data());
    });
  })
  .catch(err => {
    console.log('Error getting documents', err);
  });
```

- 実行する

```sh
node index.js
```
<img width="574" alt="スクリーンショット 2019-09-19 23.59.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/95d93386-29b8-e276-273f-202f643e10cd.png">

- ここまでで基本的なデータの登録/取得は完了です

## ExpressでAPIとして公開する

- これまではスクリプトを実行して処理を動かしていました
- [Express](https://expressjs.com/ja/)を使ってHTTPでのアクセスによってFirestoreへの登録/取得ができるようにしてみます
- 新しく作業用のフォルダを作成しセットアップ

```sh
mkdir firestore-sample-express
cd firestore-sample-express
npm init -y
npm i express body-parser firebase-admin firebase-functions
```

- index.jsを作成する
    - `ServiceAccount.json`はさっきのものを使い回す
    - まずはHelloを返すエンドポイントだけ作成

```js:index.js
const express = require('express');
const bodyParser = require('body-parser');
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const ServiceAccount = require('./ServiceAccount.json');

// Firebaseのinitialize
admin.initializeApp({ credential: admin.credential.cert(ServiceAccount) });

// Firestoreの参照を取得
const db = admin.firestore();
const docRef = db.collection('messages');

// Expressのセットアップ
const app = express();
app.use(bodyParser.json());

// GETで/helloにアクセスするとこの関数が実行される
app.get('/hello', (req, res) => {
  res.send('Hello');
});

// Expressを起動する
const port = '8080';
app.listen(port, () => console.log(`app start listening on port ${port}`));
```

- アプリを起動する

```sh
node index.js
```

- `/hello`にアクセスして動作確認

```sh
curl -X GET localhost:8080/hello
Hello
```

- messagesコレクションにデータを登録/取得するエンドポイントを追加する

```js:index.js
// 省略

app.get('/messages', async (req, res) => {
  // messagesコレクションのデータを全量取得
  const snapshots = await docRef.get();
  // レスポンスからデータ部分のみ取り出す
  const messages = snapshots.docs.map(doc => doc.data());
  res.send(messages);
});

app.post('/messages', async (req, res) => {
  const message = {
    text: req.body.text,
    timestamp: admin.firestore.FieldValue.serverTimestamp(), // このように書くとtimestampも保存できる
  };
  await docRef.add(message);
  res.send(message);
});

// 省略
```

- アプリを再起動

```sh
node index.js
```

- メッセージを登録してみる

```sh
curl -X POST -H "Content-Type: application/json" -d '{"text": "こんにちは"}' localhost:8080/messages
```
<img width="728" alt="スクリーンショット 2019-09-20 0.35.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/525ac340-b60f-2d0b-81e8-13b2e77a0961.png">

- メッセージを取得してみる

```sh
curl -X GET localhost:8080/messages
```
<img width="590" alt="スクリーンショット 2019-09-20 0.37.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/d63ee00e-8e64-e034-295c-a103d29c1aaf.png">

- これらのAPIを叩くことでWebアプリやスマホアプリからFirestoreへのデータ登録/取得ができちゃいます！
- [Expressで作ったアプリをAWS Lambdaで動かすまで](https://qiita.com/ozaki25/items/504da150d6f8318e354f)で紹介している手順と組み合わせればAPIをLambdaに公開することも簡単にできてしまいます！