Firebaseを使ってコメント機能を実装する

## はじめに
Webページにコメントフォームを埋め込み、閲覧者がコメントを投稿できる機能をシンプルに作りたいと思ったので、`Firebase`を使ってコメントの保存と取得して一覧表示をする方法をご紹介します。

## こんなものを作っていきます

![画面収録-2021-02-10-22.22.45.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/a33380c4-c941-e517-5a08-d1f35385bca7.gif)

## 前提
* 今回`Firebase`を使うのでGoogleアカウントが必要です。
* ReactやAngularなどを使ったほうがやりやすいですが、今回はピュアなJavaScriptを使って実装していきます。
* 実装には関係ないですがデザインは`UIKit`を使いました。

## Firebaseとは
Firebaseは、Googleが提供するモバイル及びWebアプリケーションのバックエンドサービスです（これを`mBaaS`と言います） 
通常、今回のようなコメント機能を実装するには、Webページだけではなくコメントを保存するデータベースや送信されたデータを処理するためのサーバーを作成する必要がありますが、`Firebase`を使えばこのサーバーをつくる工程が不要になるのでサービスの素早い開発をすることができるようになります。

## まずはフォームだけを作っていく

```html:index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>コメント</title>
    <!-- UIkit CSS -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/uikit@3.6.16/dist/css/uikit.min.css"
    />

    <!-- UIkit JS -->
    <script src="https://cdn.jsdelivr.net/npm/uikit@3.6.16/dist/js/uikit.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/uikit@3.6.16/dist/js/uikit-icons.min.js"></script>
  </head>
  <body>
    <div class="uk-container uk-padding">
      <div
        class="uk-card uk-card-default uk-card-body uk-width-1-2@m uk-margin-auto"
      >
        <h2 class="uk-card-title">コメント</h2>
        <form class="uk-form-stacked" id="commentForm">
          <div class="uk-margin">
            <label for="nickname" class="uk-form-label">ニックネーム</label>
            <div class="uk-form-controls">
              <input
                id="nickname"
                type="text"
                name="nickname"
                class="uk-input"
                placeholder="名無しさん"
              />
            </div>
          </div>
          <div class="uk-margin">
            <label for="comment" class="uk-form-label">コメント</label>
            <div class="uk-form-controls">
              <textarea
                name="comment"
                id="comment"
                cols="30"
                rows="10"
                class="uk-textarea"
                required
              ></textarea>
            </div>
          </div>
          <div class="uk-margin-large uk-flex uk-flex-center">
            <button type="submit" class="uk-button uk-button-primary">
              コメントする
            </button>
          </div>
        </form>
        <hr class="uk-divider-icon">
        <ul class="uk-comment-list" id="commentContainer">
        </ul>
      </div>
    </div>
    <script type="text/javascript" src="script.js"></script>
  </body>
</html>
```
次にとりあえずJavaScriptでは送信するデータを`console.log`で表示します。

```javascript:script.js
const form = document.getElementById('commentForm');
const nicknameInput = document.getElementById('nickname');
const commentInput = document.getElementById('comment');
const commentContainer = document.getElementById("commentContainer");

form.addEventListener('submit', (ev) => {
    console.log(nicknameInput.value, commentInput.value);
    ev.preventDefault();
})
```

ニックネームとコメントを入力して『コメントする』ボタンを押すとデベロッパーツールのコンソールに入力されたデータが表示されればOKです！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/4670a9fe-c3fc-34a6-1f71-772994341a5f.png)

## Firebaseのセットアップ
[Firebase](https://firebase.google.com/)のページにアクセスし画面右上の『ログイン』からログインしてください。
![スクリーンショット 2021-02-10 23.13.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/e353bc0e-2a96-16c4-69fd-a2c1169a2ae9.png)


ログインできたら『コンソールへ移動』からコンソールに移動してください。
![スクリーンショット 2021-02-10 23.10.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/458a2a9c-4e5a-6a8a-9016-436b336de4e0.png)

コンソールに移動したら『プロジェクトの作成』からプロジェクトを作成しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/c35bf504-2ca7-1928-6c7c-f49f41a90e78.png)

『プロジェクト名』と『Google Analytics』の設定をしてWebアプリケーションの追加をしましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/60af3115-7566-6b3f-6650-acf9c53d3bae.png)

『アプリのニックネーム』は何でも大丈夫です！
![スクリーンショット 2021-02-10 23.18.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/fb58676a-304f-9370-4bfd-fb57ee002a57.png)

アプリ名の登録が完了され表示されるHTMLタグをコピーして`index.html`のbodyタグの下部に追加してください。

```html:index.html
  <body>

    <!-- 省略 -->

    <!-- ここから追加 -->
    <!-- The core Firebase JS SDK is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/8.2.6/firebase-app.js"></script>

    <!-- TODO: Add SDKs for Firebase products that you want to use
            https://firebase.google.com/docs/web/setup#available-libraries -->

    <script>
      // Your web app's Firebase configuration
      var firebaseConfig = {
        apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        authDomain: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        projectId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        storageBucket: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        messagingSenderId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        appId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
      };
      // Initialize Firebase
      firebase.initializeApp(firebaseConfig);
    </script>
    <!-- ここまで追加 -->

    <script type="text/javascript" src="script.js"></script>
  </body>

```

## Firestoreのセットアップ
`Firestore`はFirebaseのデータベースのサービスです。
左のメニューの構築の中の`Cloud Firestore`にアクセスし『データーベースを作成』を押してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/3a068401-b018-91ef-d7cb-342bfebd5461.png)

セキュリティー保護ルールはここでは`テストモード`にしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/8e7cdf87-25c2-9c34-b780-74e9a8d0f18c.png)

ロケーションは[Firestoreのドキュメント](https://firebase.google.com/docs/firestore/locations?hl=ja)によると`asia-northeast1`が東京のようなのでとりあえず`asia-northeast1`にして『有効にする』を押して作成を完了してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/6b6ced29-d198-c89d-246f-e1695b93d1e2.png)

FirestoreのSDKを`index.html`に貼り付けてください。

```html:これをコピーしてください
<script src="https://www.gstatic.com/firebasejs/8.2.1/firebase-firestore.js"></script>
```
まえにFirebaseからコピペしたコードの周辺に貼り付ければOKです！

```html:index.html
    <!-- The core Firebase JS SDK is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/8.2.6/firebase-app.js"></script>

    <!-- ここから追加 -->
    <script src="https://www.gstatic.com/firebasejs/8.2.1/firebase-firestore.js"></script>
    <!-- ここまで追加 -->
```

## Firestoreにデータを保存する
Firestoreにデータを保存するときは次のようなコードになります。

```javascript:Firestoreにデータを保存するとき
const db = firebase.firestore();
db.collection('コレクション名')
  .add(データ)
  .then(() => {
    // 保存が完了したときの処理
  })
  .catch((err) => {
    // エラーが発生したときの処理
  }); 
```
コレクションというのはデータを保存しておくフォルダのようなものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/5eea3dc0-1163-654e-3d95-4385fa666e6b.png)
引用：[Cloud Firestore | Firebase](https://firebase.google.com/docs/firestore?authuser=0#how_does_it_work)

それではコメントをFirestoreに保存する処理を書いてきましょう

```javascript:script.js
const form = document.getElementById("commentForm");
const nicknameInput = document.getElementById("nickname");
const commentInput = document.getElementById("comment");
const commentContainer = document.getElementById("commentContainer");

// firestoreのインスタンスを初期化
const db = firebase.firestore();

form.addEventListener("submit", (ev) => {
  // formが送信されるのを防ぐ
  ev.preventDefault();

  // １．formDataのオブジェクトを作成
  const formData = {
    nickname: nicknameInput.value || "名無しのジャクソン",
    comment: commentInput.value,
  };

  console.log(formData);

  // ２．firebaseにデータを送信
  db.collection("comments")
    .add(formData)
    .then(() => {
      console.log("送信されました");

      // フォームの内容を削除
      nicknameInput.value = "";
      commentInput.value = "";
    })
    .catch((err) => {
      console.error("エラーが発生しました：", err);
    });
});
```
一気に長くなりましたね。ですがやっていることは単純で
1. formDataのオブジェクト作成する（nicknameが空のときは「名無しのジャクソン」がセットされるようにしています）
2. Firestoreにデータを送信
の２つです。

正しく出来ていればFirestoreのデータに送信した内容が保存されていると思います。
![スクリーンショット 2021-02-11 0.07.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/9c80dd1f-9f2b-4e4f-40d0-319e880cf5f1.png)

## Firestoreからデータを取得する
Firestoreに保存することができるようになったので、次はFirestoreに保存されているデータを取得して表示してましょう。

Firestoreからデータを取得するときは次のようなコードになります。

```javascript:Firestoreからデータを取得するとき
db.collection("コレクション名")
  .get()
  .then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
      // コレクションに格納されているデータ１つ１つに対する処理
      // doc.data()でデータを取得できる
      console.log(doc.data());
    });
  });
```

それではFirestoreからコメント一覧を取得して表示するコードを書いていきましょう

```javascript:script.js
const form = document.getElementById("commentForm");
const nicknameInput = document.getElementById("nickname");
const commentInput = document.getElementById("comment");
const commentContainer = document.getElementById("commentContainer");

// firestoreのインスタンスを初期化
const db = firebase.firestore();

// 最初にコメント一覧を取得して表示する。
window.addEventListener("load", (ev) => {updateComment()});

form.addEventListener("submit", (ev) => {
  // formが送信されるのを防ぐ
  ev.preventDefault();

  // formDataのオブジェクトを作成
  const formData = {
    nickname: nicknameInput.value || "名無しのジャクソン",
    comment: commentInput.value,
  };

  console.log(formData);

  // firebaseにデータを送信
  db.collection("comments")
    .add(formData)
    .then(function () {
      console.log("送信されました");
      nicknameInput.value = "";
      commentInput.value = "";

      // コメントの保存が成功したら、コメントの一覧を取得して表示する
      updateComment();
    })
    .catch(function (err) {
      console.error("エラーが発生しました：", err);
    });
});

// コメント一覧を更新する関数
function updateComment() {
  db.collection("comments")
    .get()
    .then((querySnapshot) => {
      // コメント一覧の配列
      const comments = [];

      querySnapshot.forEach((doc) => {
        comments.push(doc.data());
      });

      renderComment(comments);
    });
}

// コメントを描画する関数
function renderComment(comments) {
  commentContainer.innerHTML = '';
  comments.forEach((comment) => {
    commentContainer.innerHTML += createCommentNode(comment);
  });
}

// コメントのノードを作成してくれる関数
function createCommentNode(comment) {
  return `<li>
    <div class="uk-comment uk-comment-primary">
      <div class="uk-comment-header">
        <h5 class="uk-comment-title">${comment.nickname}</h5>
      </div>
      <div class="uk-comment-body">
        ${comment.comment}
      </div>
    </div>
  </li>`;
}
```

ここまで問題なくできていれば、最初と同じように読み込み時に投稿されているコメント一覧が表示されており、コメントの投稿が完了したときには新たに投稿したコメントが追加されると思います。

![画面収録-2021-02-10-22.22.45.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134712/a33380c4-c941-e517-5a08-d1f35385bca7.gif)

## おわりに
今回、Firebaseを使ったコメント機能をピュアなJavaScriptで実装していきました。
バックエンドの実装が必要ないので１時間もかからないで実装できましたが、普段VueやReactなどのJavaScriptフレームワークを使っているのでピュアJavaScriptでコードを記述するのはやっぱりめんどくさいなと思いました。
また、FirebaseのAPIキーなどをそのまま`index.html`に埋め込んでいるため、これを見られてしまえば、勝手に他のページからもコメントを書き込まれてしまうと思います。実際にサービスとして実装する際にはそのへんのことも考慮に入れる必要があるでしょう。