【Next.js】Cloud FirestoreをNext.jsに組み込みデータ取得

f:id:nekorokkekun:20190921172341p:plain:w1000
こちらではNext.jsのプロジェクトにFirebaseのCloud Firestoreを組み込む方法について解説をしていきます。

Firebaseでプロジェクトの作成〜コレクションの作成

Firebaseにアクセスし、Consoleへ移動しましょう。

f:id:nekorokkekun:20190921093319p:plain

そしてプロジェクトの追加をします。
プロジェクト名は任意で構いません。

プロジェクトが生成されると以下のようなページに遷移します。
f:id:nekorokkekun:20190921093631p:plain

サイドメニューの「開発」から「Database」を選択します。するとCloud Firestoreのページに遷移しました。

「データベースの作成」ボタンをクリックすると、モーダルウィンドウが出てきます。

「テストモードで開始」を選択して「次へ」を押すと、ロケーションを選ぶことができるので東京リージョンの「asia-northeast1」を選びましょう。

「完了」ボタンを押すと、データベースの作成が始まります。

ここまでの手順で以下の画面に遷移します。
f:id:nekorokkekun:20190921095118p:plain

次に「コレクションの開始」をクリックします。
ここからMySQLでいうところのテーブル作成となります。

今回はpostsというドキュメントIDで設定しましょう。

MySQLで例えると以下のようなコレクションを作成します。
f:id:nekorokkekun:20190921095632p:plain

フィールド名とValueを設定します。
f:id:nekorokkekun:20190921095754p:plain

そうすると設定した中身でコレクションができましたね。
f:id:nekorokkekun:20190921100020p:plain

あまり聞きなれない単語が多いかもしれませんが、

  • コレクション = テーブル
  • 一意のID = PRIMARY KEY
  • ドキュメント = カラムとレコード

というイメージです。

Next.js側の設定

次にCloud FirestoreをNext.js側に組み込んでいきます。

組み込むにあたってはNext.jsの新規プロジェクトをnpm init で立ち上げてもらってもいいですし、
同じ環境下で行いたいということであれば、こちらのブランチをgit cloneしてもらっても構いません。


まずはルートディレクトリに「.env」を作成しましょう。
こちらにCloud Firestoreにおける設定情報を記入していくため、「.gitignore」を開き、「.env」を追加しておきます。

.gitignore

node_modules
.next
#追加
.env

Firebaseに戻り、サイドメニューの「Project Overview」を選択。
画像の垢枠の部分をクリックしましょう。
f:id:nekorokkekun:20190921101148p:plain


ウェブアプリの登録画面が出てくるので、こちらも任意で命名します。
その後、スクリプトタグが出てくるため以下の部分をコピーしましょう。

  var firebaseConfig = {
    apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    authDomain: "pf-fanclub.firebaseapp.com",
    databaseURL: "https://pf-fanclub.firebaseio.com",
    projectId: "pf-fanclub",
    storageBucket: "pf-fanclub.appspot.com",
    messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    appId: "1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);

そして、先ほど作成した.envにペーストするのですが、スクリプトを成形する必要があります。

.env

FIREBASE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_AUTH_DOMAIN=pf-fanclub.firebaseapp.com
FIREBASE_DATABASE_URL=https://pf-fanclub.firebaseio.com
FIREBASE_PROJECT_ID=pf-fanclub
FIREBASE_STORAGE_BUCKET=pf-fanclub.appspot.com
FIREBASE_MESSAGING_SENDER_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_APP_ID=1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

「""」や「,」などは削除し、環境変数として命名しましょう。

次にデータベース設定を行っていきます。

環境変数をNode.js下で扱うためのdotenvモジュールをインストールしておきましょう。

npm install --save dotenv

dotenvモジュールについては以下の記事に分かりやすい解説がありましたので引用します。

Node.js のプログラムから環境変数を参照するには process.env を参照します。

ユーザー設定を環境変数で行うようにしているアプリはよくあるのですが、たかが 1 つのアプリのために環境変数を設定するのは嫌だというユーザーは少なからずいます(設定がどこで行われているのかわかりにくいという理由もあります)。

dotenv モジュールを使用すると、カレントディレクトリに置かれた .env ファイルを読み込み、そこに記述されたキー&バリューのペアを process.env 経由で参照できるようになります。

つまり、ユーザはアプリの設定を、従来通り環境変数で行うこともできるし、.env ファイルでも行うことができるようになります。 環境変数を使ってアプリの挙動を変えるような実装をしている場合は、.env ファイルによる設定もサポートしておくと親切です。
環境変数の代わりに .env ファイルを使用する (dotenv) | まくまくNode.jsノート

次にfirebaseをnpm経由でインストールしていきます。

npm install --save firebase

次にルートディレクトリに以下のディレクトリとファイルを作成しましょう。

lib/db.js

import firebase from 'firebase/app'
import 'firebase/firestore'

let db;
try {
  const config = {
        apiKey: process.env.FIREBASE_API_KEY,
        authDomain: process.env.FIREBASE_AUTH_DOMAIN,
        databaseURL: process.env.FIREBASE_DATABASE_URL,
        projectId: process.env.FIREBASE_PROJECT_ID,
        storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
        messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
        appId: process.env.FIREBASE_APP_ID
    };
    firebase.initializeApp(config);
    // Firestoreインスタンスを作成
    db = firebase.firestore();
  } catch (error) {
    console.log(error);
  }

  module.exports = {
    // 本来、initializeAppによる初期化は一度きりのため、
    // 初期化の結果のみを切り出してexportする
    db
  };

Firebaseで表示されたスクリプトを先ほど.envに設定した環境変数に置き換えました。
これで外部からデータベースの設定情報を閲覧されることはありません。

また環境変数の前にprocess.env.を付与することで、dotenvモジュールによってprocess.envを経由した参照になります。

次に以下のファイルをルートディレクトリに作成しましょう。

next.config.js

const webpack = require('webpack');
require('dotenv').config();

module.exports = {
  webpack: config => {
    const env = Object.keys(process.env).reduce((acc, curr) => {
      acc[`process.env.${curr}`] = JSON.stringify(process.env[curr]);
      return acc;
    }, {});

    config.plugins.push(new webpack.DefinePlugin(env));

    return config;
  }
};

もしこの段階で

Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).

といったエラーが出た場合、色々な原因があり、それに応じた解決策があるようですが、initializeAppを複数回行った覚えがないという場合には以下の方法で解決できるかもしれません。(私の場合はキャッシュが消して開発環境を再構築することで解決できました)
Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).というエラーに悩まされたときに - Qiita


最後に表示部分を作成します。
pages/Posts.js

import { db } from '../lib/db';
import React from 'react'

 export default class Posts extends React.Component {
  static async getInitialProps() {
    // db.jsのfirebaseのDB接続ファンクション
    // DBのpostsコレクション内を全て取得した結果 = result
    let result = await new Promise((resolve, reject) => {
      db.collection('posts')
      .get()
      .then(snapshot => {
        let data = []
        snapshot.forEach((doc) => {
          data.push(
            Object.assign({
              id: doc.id
            }, doc.data())
          )
        })
        resolve(data)
      }).catch(error => {
        reject([])
      })
    })
    return {posts: result}
  }

  handleDelete = (id) => {
    console.log(id)
  }

  render() {
    const posts = this.props.posts
    return (
        <React.Fragment>
                {posts.map(post =>
                    <div className="post" key={post.id}>
                        <h2>
                            {post.title}
                        </h2>
                        <p>
                            {post.body}
                        </p>
                        <button onClick={this.handleDelete.bind(this, post.id)}>削除</button>
                    </div>
                    )}
            <style jsx>{`
            .post {
                width: 40%;
                border: 1px solid black;
                background-color: gray;
                margin-bottom: 10px;
            }
            `}</style>
        </React.Fragment>
    );
  }
}

これでFirebaseのCloud Firestoreに登録されているデータが表示されたはずです!