チャットApp(firebaseを使ってみる)

前回firebaseを使えるように設定したので、今回実際に使ってみようと思います。流れとしては、以下のようになります。

1. 各種キーのファイルを作成

firebaseの各種キーのようなものは、環境変数として扱います。環境変数は「.env」というファイルで管理するのですが、それを読み込むためにdotenvモジュールをインストールします。

yarn add dotenv

次に「.env」ファイルをルート直下に作成し以下のように各種キーを設定します。Xで書いてる部分にそれぞれのキーが入ります。

FB_API_KEY=XXXXXXXXXXXXXXXXXXXXX
FB_AUTH_DOMAIN=XXXXXXXXXXXXXXXXXXXXXX
FB_DATBASE_URL=XXXXXXXXXXXXXXXXXXXXXX
FB_PROJECT_ID=XXXXXXXXXXXXXXXXXXXXXXX
FB_STORAGE_BUCKET=XXXXXXXXXXXXXXXXXXXXX
FB_MESSAGING_SENDER_ID =XXXXXXXXXXXXXXXXXX
FB_APP_ID=XXXXXXXXXXXXXXXXXXXXXXX
FB_MEASUREMENT_ID=XXXXXXXXXXXXXXXXXXXX


キーの部分をXで置き換えているところからわかるように、これらのキーは自分だけで管理するものであり、外部に見せるべきではありません。もちろんGithubにあげるなんて言語道断です。そこで「.env」をgitignoreに追加してあげましょう。gitignoreに追加したものはリモートリポジトリに送られることはありません。

スクリーンショット 2020-08-13 9.48.14


このままだと、プロジェクトからも.envを読み込めないので、その設定をしましょう。ルート直下にnext.config.jsファイルを作り以下のように記述します。

// @ts-ignore ←jsファイルなのにtypescriptが幅を効かすってなんなのよ?
const webpack = require("webpack");
require("dotenv").config();
module.exports = {
   // dotenv
   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;
   }
}

ここでdotenvモジュールを使いプロジェクトからも使えるようにしてあげます。余談ですがjsファイルなのにtypescriptの解析ツールが幅を効かせて最初の行はエラーになっていたのでts-ignoreで無視させています。ここら辺は設定で解決出来ると思うのですが、調べるのが大変なので今回はこれで対処します。

2. firebaseオブジェクトを生成し各種キーで初期化

プロジェクトへのパスが通ったのでfirebaseを使うためのロジックを組んでいきます。今回はfunctionsというディレクトリをルート直下に作りそこにfirebase.tsというファイルを作ります。ここでfirebaseオブジェクトを初期化してエクスポートし、各ファイルで使えるようにします。ここでnext.jsならではのエラーが出るのでそこを注釈しておきます。

素のreact等なら以下のような記載で良いのですが、next.jsの場合通常一度しか実行されないinitializeAppが複数回実行されてエラーになります。

import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
const firebaseConfig = {
 apiKey: process.env.REACT_APP_API_KEY,
 authDomain: process.env.REACT_APP_AUTH_DOMAIN,
 databaseURL: process.env.REACT_APP_DATABASE_URL,
 projectId: process.env.REACT_APP_PROJECT_ID,
 storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
 messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
 appId: process.env.REACT_APP_APP_ID,
 measurementId: process.env.REACT_APP_MEASUREMENT_ID,
}
// OAuth
export const googleProvider  = new firebase.auth.GoogleAuthProvider();
export const twitterProvider = new firebase.auth.TwitterAuthProvider();

firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL);

export const db = firebase.firestore();
export default firebase;

スクリーンショット 2020-08-13 10.07.17

そこでtry-catch文複数回の実行を回避します。

import firebase from "firebase";
import "firebase/auth";
import "firebase/firestore";
let FB:any;
export         let FBdb:firebase.firestore.Firestore;
export         let FBstorage:firebase.storage.Storage;
export         let googleProvider:firebase.auth.GoogleAuthProvider;
export         let twitterProvider:firebase.auth.TwitterAuthProvider;
export default FB;
try {
   const firebaseConfig = {
       apiKey:            process.env.FB_API_KEY,
       authDomain:        process.env.FB_AUTH_DOMAIN,
       databaseURL:       process.env.FB_DATABASE_URL,
       projectId:         process.env.FB_PROJECT_ID,
       storageBucket:     process.env.FB_STORAGE_BUCKET,
       messagingSenderId: process.env.FB_MESSAGING_SENDER_ID,
       appId:             process.env.FB_APP_ID,
       measurementId:     process.env.FB_MEASUREMENT_ID,
   };
   
   firebase.initializeApp(firebaseConfig);
   
   firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL);
   
   FBdb            = firebase.firestore();
   FBstorage       = firebase.storage();
   googleProvider  = new firebase.auth.GoogleAuthProvider();
   twitterProvider = new firebase.auth.TwitterAuthProvider();
   FB = firebase;
} catch (error) {
   console.log(error);
}

3. firebaseに書き込み・読み込みの関数を作成

次にfirebaseのデータベースであるfirestoreに書き込み・読み込みを行う関数を作ります。functionディレクトリにdatabaseFunctions.tsというファイルを作成し、以下を記述します。関数をページファイルに直書きしても良いのですが、分けておくとコードが見やすくなったり、後からスケールしやすくなったりと良いことが多いです。

import { FBdb } from "./firebase";
// firestoreに書き込み
export const setData = async(texts: string[]):Promise<string> => {
   const message:string = await FBdb.collection("foo").doc("bar").set({
       texts: texts
   })
   .then(() => {
       return "set data successfully!";
   })
   .catch(() => {
       return "error has occured!";
   });
   return message;
}
// firestoreから読み込み
export const getData = async() => {
   const data = await FBdb.collection("foo").doc("bar").get()
   .then(result => {
       return result.data();
   });
   return data;
}

非同期処理の記述が入るので慣れてない人には難しく感じるかもしれませんが、出来るようになると世界が広がります!
直書きだと非同期処理のasync, awaitを避けて書く方法もあるのですが、直書きをした結果後から手の付けられないプロジェクトが出来てしまった経験があります。みなさんはそんなこと無いように気をつけてください。

4. indexで関数を使用し、firestore(database)への簡単な書き込み・読み込みをしてみる。

さて、読み込み・書き込みの準備は出来たので、pagesディレクトリのindexファイルを使って読み込み・書き込みが出来るかチェックしてみましょう。

その前にヘッドのメタデータのファイルを作りましょう。ヘッドに表示するメタデータのうちタイトルなどのデータはどのページも共通なので、別で作って読み込むようにした方が楽だし間違いがありません。components/atomというディレクトリを作り、その中に以下のファイルを作成します。

basicData.ts

const basicData = {
   title: "ちゃちゃっとチャットせんといかんと?",
}
export default basicData;

head.tsx

import Head      from "next/head";
import basicData from "./basicData";
export default function BasicHead() {
   return (
       <Head>
           <title>{ basicData.title }</title>
           <link rel="icon" href="/favicon.ico" />
       </Head>
   );
}
​

これで、ヘッドデータを読み込むことが出来ます。次に元々あるindex.jsは削除して新たにindex.tsxというファイルを作成します。そして以下を記述してください。

import BasicHead from "../components/atom/head";
import basicData from "../components/atom/basicData";
// firebaseを試すために読み込む
import { useState } from "react";
import { setData, getData } from "../functions/databaseFunctions";

export default function Home() {
   // 入力されたテキスト
   const [text, setText]       = useState("");
   // DBに保存した結果の表示
   const [message, setMessage] = useState("");
   // DBに保存するテキストのリスト
   const [texts, setTexts]     = useState<string[]>([]);
   // DBから読み込んだデータのリスト(textsと一致する)
   const [datas, setDatas]     = useState([]);
   // 入力された文字を変数textに反映
   const handleChange = (event:React.ChangeEvent<HTMLInputElement>) => {
       setText(event.target.value);
   }
   // DBに保存を実行
   const handleClick = async() => {
       const tempTexts = [...texts];
       tempTexts.push(text);
       setTexts(tempTexts);
       const msg = await setData(tempTexts);
       setMessage(`${msg}: ${text}`);
   }
   // DBからデータを読み込む
   const reloadData = async() => {
       const tempData = await getData();
       console.log(tempData);
       setDatas(tempData.texts);
   }
   return (
       <div>
           <BasicHead />
           <h1>{ basicData.title }</h1>
           <input 
               type="text" 
               value={ text } 
               onChange={ handleChange }
           />
           <button 
               type="button"
               onClick={ handleClick }
           >保存</button>
           <button 
               type="button"
               onClick={ reloadData }
           >リロード</button>
           <p>{ message }</p>
           <ul>
               { datas.map((data, index) => (
                   <li key={ index }>{ data }</li>
               ))}
           </ul>
       </div>
   );
}

ここでは、インプットタグに書いた文章を保存ボタンでfirestoreに書き込み、リロードボタンでこれまでに書き込んだ文章をリスト形式で表示するようにしています。このファイルは、後からルートページに書き換えるつもりなので、スタイリング等は行っていません。実行結果は以下のようになります。

スクリーンショット 2020-08-12 17.56.44

スクリーンショット 2020-08-12 18.00.50

firestoreにもきちんと保存されています。

スクリーンショット 2020-08-12 18.00.40

5. デプロイしてみる

vercelにデプロイするためにgithubにプッシュします。firebaseというブランチを切ってそのブランチにプッシュします。vercelの良いところはgithubと連携させると、マスターブランチにプルリクエストを送った時にプレビューを生成してくれるところです。これで本番環境を汚すことなく、新しい機能の確認が出来ます。

スクリーンショット 2020-08-12 18.13.36

プレビューを確認してみると、firestoreへの通信が上手くいってません。

スクリーンショット 2020-08-12 18.20.07

そういえば「.env」はgithubにあげていないので各種キーが設定されていない状態ですね。調べるとvercelには環境変数を設定するページがあるのでそこで各種キーを設定します。自動で暗号化してくれるので安心です。

スクリーンショット 2020-08-13 9.24.18

あとは、next.config.jsに追記して以下のようにします。

// @ts-ignore ←jsファイルなのにtypescriptが幅を効かすってなんなのよ?
const webpack = require("webpack");
require("dotenv").config();
module.exports = {
   // dotenv
   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;
   },
   // vercelでの環境変数のパスを通す
   env: {
       apiKey:            process.env.FB_API_KEY,
       authDomain:        process.env.FB_AUTH_DOMAIN,
       databaseURL:       process.env.FB_DATABASE_URL,
       projectId:         process.env.FB_PROJECT_ID,
       storageBucket:     process.env.FB_STORAGE_BUCKET,
       messagingSenderId: process.env.FB_MESSAGING_SENDER_ID,
       appId:             process.env.FB_APP_ID,
       measurementId:     process.env.FB_MEASUREMENT_ID,
   }
}

これでもう一度プッシュして確認すると、

スクリーンショット 2020-08-13 9.31.30

上手くいきました。これでfirebaseとの通信が出来ます。ちなみにこれまでのディレクトリ構成はこのような感じです。

スクリーンショット 2020-08-13 10.54.00


この記事が気に入ったらサポートをしてみませんか?