Firebaseのバックエンドを利用したお問合せフォームの作り方!
Firebaseのバックエンドでのお問合せフォームの処理のやり方を紹介しました。
この記事では、バックエンド処理でお問合せフォームのサービスを実現する方法について解説しています。今回紹介する例の方針は:
* シンプルなバックエンドの処理の組み込み方の例
* フロントエンドとの連携
に重点を置きました。
シンプルにするために、フロントエンドの実装はシンプルなHTMLファイルで対応しています。今回の例ではフロントエンドでは一切プログラムは利用していません。
Firebaseのプロジェクトフォルダ
FirebaseのプロジェクトをFirebaseコンソールで作成して、Firebaseのプロジェクトフォルダ(公開用のデータを置くフォルダ)を作成します。
Firebaseコンソールでは、データベース(Cloud Firestore)の作成を行います。 セキュリティルールは本番用(production mode)で問題ありません。 今回は、データベースにはバックエンド側からしかアクセスしないので、シンプルにフロントエンドからのアクセスは禁止にして問題ありません。 セキュリティルールの設定例です。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
フロントエンドからのアクセスを全て禁止にしてあります。
Firebaseのプロジェクトフォルダの初期化
Firebaseのプロジェクトフォルダーを「firebase init」を実行して初期化します。
実行の前に、「firebase-tools」と「firebase-admin」のインストールが必要です。また、Firebaseにログインしていない場合は「firebase login」を実行してログインしたうえで実行してください。
$ npm install -g firebase-tools
$ npm install --save firebase-admin
$ firebase login
$ firebase init
初期化で選択する機能は
functions (ファンクション/バックエンド機能)
hosting (ホスティング機能)
です。また、この解説ではJavascriptではなく、Typescriptを使った例を紹介しています。
フォルダーの構成は
「firebase init」を実行して、ホスティング機能とバックエンド機能を選択するとFirebaseのプロジェクトフォルダは以下のような構成になります。
public -- HTMLなどのフロントエンドのファイルを置く
function -- バックエンドのファイルを置く
src -- Typescriptのソースコードを置く
lib -- Javascriptにコンパイルされたファイルが作られる
今回の例ではバックエンドのファイルは、functions/srcの下に、「index.ts」を作成します。 publicの下には、お問合せフォームなど作成したHTMLのファイルやCSSのファイルを置きます。
後で説明する、秘密鍵(Private key)のファイルはfunctionsの下に置きます。
また、Firebaseの設定を行う「firebase.json」は以下のように変更します。 ポイントは、「hosting」の設定に「rewrites」を追加します。この設定で全てのパスを「app」のファンクションで処理するようにします。
{
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
},
"hosting": {
"public": "public",
"rewrites":[{
"source":"**",
"function":"app"
}],
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}
Expressのパッケージのインストール
Firebaseのプロジェクトフォルダのfunctionsに移動して、「Express」のパッケージをインストールします。
$ npm install express
これでフォルダーの準備は完了です。
お問合せフォームのページ
お問合せフォームのページはシンプルなHTMLで記述しています。
お問合せフォームで利用している項目は:
* E-Mailアドレス(必須)
* 名前
* お問合せ件名
* お問合せ内容(必須)
以上の項目を入力するためのフォームを記述しています。以下が記述例です。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./style//style.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2"
crossorigin="anonymous"
/>
<title>お問合せフォーム</title>
</head>
<body>
<div class="header">
<label>Silicon Valley Super Ware</label>
</div>
<h1>お問合せ</h1>
<p>E-Mailアドレスとお問合せ内容は必ず入力してください。</p>
<div class="query_form">
<form action="/post" method="post">
<div class="form-group">
<label>E-Mail</label>
<input
class="form-control"
type="text"
name="email"
placeholder="E-Mailアドレスを入力してください"
/>
</div>
<div class="form-group">
<label>お名前</label>
<input
class="form-control"
type="text"
name="name"
placeholder="お名前を入力してください"
/>
</div>
<div class="form-group">
<label>お問合せ件名</label>
<input
class="form-control"
type="text"
name="subject"
placeholder="お問合せ件名を入力してください"
/>
</div>
<div class="form-group">
<label>お問合せ内容</label>
<textarea
class="form-control"
name="message"
placeholder="お問合せ内容を入力してください"
></textarea>
</div>
<button class="btn btn-primary" type="submit">送信</button>
</form>
</div>
<div class="footer">
<footer>
Copyright(c) 2020 by Silicon Valley Super Ware, all rights reserved.
</footer>
</div>
</body>
</html>
フォームのポイントは、「送信ボタン」が押されるとフォームをバックエンドに送るようにしています。 以下のフォームの記述は、「送信ボタン」が押されたら、「/post」というサーバー側のルート(パス)にデータを送るようにしています。送信の際はHTTPのPOSTメソッドを使うように指定しています。
<form action="/post" method="post">
ボタンの方は、「type="submit"」にしているので、ボタンが押されたらフォームのデータを送信するようになっています。
<button class="btn btn-primary" type="submit">送信</button>
それぞれの入力には名前を付けます。E-Mailアドレスの入力のDOMには、「name="email"」という名前をつけているので、データを送る際は「email」のタグがつけられます。
<input
class="form-control"
type="text"
name="email"
placeholder="E-Mailアドレスを入力してください"
/>
バックエンドの処理
バックエンド側の処理は前回解説したコードとほぼ同じです。 バックエンドのコードのサンプルです。
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import * as express from "express";
// Firebase Admin SDK の秘密鍵(Secret Key)
const serviceAccount = require("../serviceAccountKey.json");
// お問合せを保存するコレクション
const FB_COLLECTION_QUERY = "query";
// お問合せフォームのバックエンド処理のURL
const BACKEND_QUERY_URL = "/post";
// 処理が成功した場合のURL
const SUCCESS_URL = "success.html";
// 処理が失敗した場合のURL
const ERROR_URL = "error.html";
// Express サーバー
const app = express();
// Firebaseの初期化
const firebaseAdmin: admin.app.App = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://sample-xxxxx.firebaseio.com",
});
// お問い合わせのURL
app.get(BACKEND_QUERY_URL, (req: express.Request, res: express.Response) => {
const email = req.query.email ? req.query.email : "";
const subject = req.query.subject ? req.query.subject : "";
const message = req.query.message ? req.query.message : "";
const name = req.query.name ? req.query.name : "";
const query = {
email: email,
subject: subject,
message: message,
name: name,
};
if (email !== "" && message !== "") {
// Fiebaseにお問い合わせを保存
firebaseAdmin
.firestore()
.collection(FB_COLLECTION_QUERY)
.add(query)
.then((doc) => {
// 処理成功
console.log(doc.id);
// レスポンスの返信
res.end(res.redirect(SUCCESS_URL));
})
.catch((error: any) => {
// 処理失敗
console.log(error.message);
// レスポンスの返信
res.end(res.redirect(ERROR_URL));
});
} else {
// レスポンスの返信
res.end(res.redirect(ERROR_URL));
}
});
// お問い合わせのURL
app.post(BACKEND_QUERY_URL, (req: express.Request, res: express.Response) => {
const email = req.body.email ? req.body.email : "";
const subject = req.body.subject ? req.body.subject : "";
const message = req.body.message ? req.body.message : "";
const name = req.body.name ? req.body.name : "";
const query = {
email: email,
subject: subject,
message: message,
name: name,
};
if (email !== "" && message !== "") {
// Fiebaseにお問い合わせを保存
firebaseAdmin
.firestore()
.collection(FB_COLLECTION_QUERY)
.add(query)
.then((doc) => {
// 処理成功
console.log(doc.id);
// レスポンスの返信
res.end(res.redirect(SUCCESS_URL));
})
.catch((error: any) => {
// 処理失敗
console.log(error.message);
// レスポンスの返信
res.end(res.redirect(ERROR_URL));
});
} else {
// レスポンスの返信
res.end(res.redirect(ERROR_URL));
}
});
// Functionsのエキスポート
exports.app = functions.https.onRequest(app);
前回のコードも残してあります。前回のコードはWebブラウザからバックエンドに簡単にリクエストを送るために、WebブラウザのURL入力欄に直接入力できるようにHTTPのメソッドはGETを利用していました。
今回はフォームから送信するために、HTTPのメソッドはPOSTを利用します。
違いはURLにデータを埋め込むか、データはURLからは別に送るかという違いがあります。
app.post(BACKEND_QUERY_URL, (req: express.Request, res: express.Response) => {
const email = req.body.email ? req.body.email : "";
const subject = req.body.subject ? req.body.subject : "";
const message = req.body.message ? req.body.message : "";
const name = req.body.name ? req.body.name : "";
const query = {
email: email,
subject: subject,
message: message,
name: name,
};
.......
HTTPのPOSTで送られたデータは、「req.body」に格納されています。Firebaseのデータベースに入れる際に値が「undefined」になっているとエラーになるので、値がない場合は「""」を入れています。
Firebaseのデータベースに保存する部分です。 E-Mailアドレスとお問合せ内容が空欄の場合はエラーにしています。
処理が成功した場合は、「SUCCESS_URL」に処理がエラーになった場合は「ERROR_URL」にリダイレクトするようにしています。 ここで変数(定数)を使っているのは、コード中にファイル名を直接書いてしまうと、後で変更する際に面倒なので、変数(定数)にして一か所にまとめてあります。ちょっとした書き方の違いですが後でコードを変更したりするときに大きな差がでます。
if (email !== "" && message !== "") {
// Fiebaseにお問い合わせを保存
firebaseAdmin
.firestore()
.collection(FB_COLLECTION_QUERY)
.add(query)
.then((doc) => {
// 処理成功
console.log(doc.id);
// レスポンスの返信
res.end(res.redirect(SUCCESS_URL));
})
.catch((error: any) => {
// 処理失敗
console.log(error.message);
// レスポンスの返信
res.end(res.redirect(ERROR_URL));
});
} else {
// レスポンスの返信
res.end(res.redirect(ERROR_URL));
}
バックエンドのコードは以上です。
前回も説明していますが、バックエンドからFirebaseのデータベースにアクセスする場合、秘密鍵(Private key)が必要になります。秘密鍵はFierebaseコンソールの「設定(Setting)」の「サービスアカウント(Service accounts)」で取得できます。秘密鍵が含まれたファイルを「serviceAccountKey.json」という名前で保存して、Firebaseのプロジェクトフォルダの下にあるfunctionsにコピーします。
初期化は以下のコードで行っています。
// Firebaseの初期化
const firebaseAdmin: admin.app.App = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://sample-xxxxx.firebaseio.com",
});
データベースのURLも初期化時に必要です。これをやらないと、データベースにアクセスした時にエラーになります。
他のHTMLファイルも用意します。
今回は実際のサイトではないのでとりあえず違う表示ができれば良いと思います。 これらをFirebaseのプロジェクトフォルダのpublicの下に保存します。 CSSファイルなどもこのフォルダーに保存します。
* success.html
* error.html
* index.html
success.html
お問い合わせの送信処理が成功したときに表示するHTML
error.html
お問い合わせの送信処理が失敗したときに表示するHTML
index.html
最初に表示するページ
テスト用のサーバーで動作確認
全てのファイルがそろったら
$ firebase serve
を実行して、動作確認をします。標準の設定の場合「localhost:5000」でアクセスできます。「localhost:5000/query.html」にお問合せフォームを置いた場合このページにアクセスして、データを送って、Firebaseにデータが格納されて、成功のページが表示されればOKです。
インターネットへの公開(デプロイ)
公開は、「firebase deploy」を実行すればインターネット上に公開されてサービスが稼働します。バックエンドでNode.jsのVersion 10.x 以降を利用する場合、有料プランに設定を変更しないとエラーになります。
有料プランにしても、無料利用枠を超えない限り無料で利用できます。
まとめ
以上が、Firebaseのバックエンドを利用したサービスの実装例です。 当然ですが、前回紹介したようなバックエンドのみではサービスとしては成り立ちません。 実際はフロントエンドでユーザーが利用するUIをHTMLやフロントエンドのフレームワークである、React/Vueなどで実装する必要があります。
フロントエンドに大量のコードを書くことも可能ですが、バックエンドに処理をまとめてしまうとフロントエンドはシンプルにすることが可能です。
JavascriptをサポートしないWebブラウザの対応
シンプルなセキュリティルールなどが利点です
サービスの機能や内容に合わせて、フロントエンドとバックエンドの処理を選択することが実際のサービスの設計では必要です。
Firebaseを利用すると、どちらの場合でも対応可能です。実際にサービスを作ってみて処理の移動も比較的簡単にできるのは大きな魅力です。