FirebaseとSendGridSendGridで作るメルマガの登録フォーム

記事
IT・テクノロジー

FirebaseとSendGridSendGridで作るメルマガの登録フォーム

FirebaseとSendGridを使ってメルマガ(ニュースレター/News letter)を実装する際に、メルマガ(ニュースレター/News letter)の登録フォームを作成する方法について解説しています。
SendGridのサービスを使う場合、バックエンドの実装が必要です。登録フォーム自体はフロントエンドからも実装できますが、バックエンドに実装をまとめてしまうと実装がシンプルになります。 バックエンド(サーバー)側でレンダリングを行う場合、最近ではNEXTやNUXTのフレームワークを利用するケースも多いですが、シンプルな実装の場合、もう少し手軽に実装できる方法に、「express-handlebar」を使う方法もあります。
この記事では登録フォームを「express-handlebar」を利用して実装する方法を紹介しています。

「express-handlebar」とは?

HTMLのテンプレートです。簡単なHTMLのテンプレートを元に、サーバー側でHTMLを作る場合に利用できます。 テンプレートエンジンと呼ばれています。いろいろな実装が公開されていますが、今回は「express-handlebar」を紹介します。
実装の準備
プロジェクトフォルダの作成
まずは、メルマガのサービスのためのフォルダを作成してください。フォルダを作成したら作成したフォルダに移動して、Firebaseの初期化を行います。
$ npm install -g firebase-tools
$ npm install --save firebase-admin
$ firebaee login
$ firebase init
Firebaseを利用するための基本的なステップです。「firebase init」では、「ホスティング(hosting)」と「ファンクション(functions)」を選択します。また、この記事はTypescript利用を前提に書かれています。

外部モージュールのインストール

次に、「firebase init」で作成されたフォルダのfunctionsに移動して、必要な外部モジュールをインストールします。
今回の例で利用する外部モジュールです。
* axios: HTTPアクセスのためのモジュール
* express: サーバー側の処理のフレームワーク
* express-handlebar:テンプレートエンジン
* path: プログラム中で使うパスのツール
上記のモジュールをnpmを使ってインストールします。
$ npm install axios express express-handlebar path
$ npm install --save-dev @types/axios @types/express

express-handlebarで使用するテンプレートの準備

express-handlebarで使用するテンプレートの準備をします。
フォルダーの作成
* functions/viewsを作成します。(functionsフォルダの下に、「vies」を作成)
* functions/views/layoutを作成します。(functions/viewsの下に「layout」を作成)
viewsの下に各ページのテンプレートファイルを置きます。 views/layoutの下に、「layout.hbs」というファイルを作成します。このファイルはメニューやフッターなど全部のページで使う記述を書いておきます。
テンプレートの準備
最初に、ページのテンプレートを作ります。ナビゲーションバーやCDNなどの読み込みをlayout.hbsに書きます。こうすることで、繰り返し各ページで同じ記述を書かないで済みます。また、修正も一つのファイルを修正するだけで全てのページが更新されるので、サイトの管理も簡単になります。
以下がlayout.hbsの例です
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
    <!--タイトルの文字列を埋め込むための記述-->
    <title>{{ title }}</title>
    <link rel="stylesheet" href="/styles/style.css">
</head>
<body>
    <!--ナビゲーションバー -->
    <div class="navigation_bar">
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <a class="navbar-brand" href="/">Silicon Valley Super Ware</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01"
                aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarColor01">
                <ul class="navbar-nav mr-auto">
                    <li class="nav-item active">
                        <a class="nav-link" href="/">ホーム <span class="sr-only">(current)</span></a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/signup">登録</a>
                    </li>
                </ul>
            </div>
        </nav>
    </div>
    <div id="contents">
        <!--各ページのテンプレートを埋め込むための記述-->
{{{ body }}}
    </div>
    <!--ページのフッター-->
    <div class="footer">
        <footer>
            Copyright(c) 2017-2020 by Silicon Valley Super Ware, all rights
            reserved.
        </footer>
    </div>
</body>
</html>
次に登録フォームのテンプレートを作ります。 登録フォームの例
{{#if condition}}
<div>
    <h2>ご登録</h2>
    <p>(*)Emailアドレスは必須です。</p>
</div>
<div class="signup_form">
    <form method="post">
        <div class="form-group">
            <label>Email アドレス<span class="required"> (*)必須</span></label>
            <input type="email" name="email" class="form-control" aria-describedby="emailHelp"
                placeholder="Emailアドレスを入力してください" />
        </div>
        <button type="submit" class="btn btn-primary">登録</button>
    </form>
</div>
{{else}}
<div class="sub_header">
    <h2>登録手続きでエラーが発生しました</h2>
    <p>お手数ですが、tomonori.hirai@sv-sw.comまでお知らせください。</p>
    <p>お手数をおかけしますがよろしくお願いしますします。</p>
</div>
{{/if}}
フォームの表示と登録処理でエラーがあった場合の処理を、別々に呼び出すことができます。 今回は登録フォームを「E-Mailアドレス」だけにしています。これは、登録に必要な情報が多くなるほど登録する可能性が下がるというのが統計的にわかっています。最低限の情報の「E-Mailアドレス」のみにすることで、登録してもらう確率があがるのを期待しています。

バックエンドの実装

バックエンドの処理の実装(ファンクション)を実装します。
バックエンドのフレームワークexpressとexpress-handlebarの初期設定をします。
// expressの初期化
const app:express.Express = express();
// exprss-handlebarの初期設定
// 基本フォルダと拡張子(extension)のテンプレートファイルの設定
app.engine(
      "hbs",
      hbs({
        extname: CONSTANT.HBS_EXT,
        defaultLayout: CONSTANT.DEFAULT_LAYOUT,
        layoutDir: path.join(__dirname, CONSTANT.DEFAULT_LAYOUT_FOLDER),
      })
    );
// 各ページのテンプレートのフォルダの設定
app.set("views", path.join(CONSTANT.DEFAULT_VIEW_FOLDER));
// テンプレートエンジンの設定
app.set("view engine", "hbs");
サインアップページのフォームを表示させるための処理です。functions/viewの下に、フォームのページ(signup)のテンプレート[signup.hbs]を作成して、「/signup」でアクセスできるようにします。 (http://localhost/signupやhttps://sv-sw.com/signupでアクセスせきるように設定します。)
これが、ブラウザで「/signup」が指定された場合の処理をする部分です。
// Sign Up Page (form)
app.get(
    CONSTANT.API_SIGNUP,
    (req: express.Request, res: express.Response) => {
        res.render("signup", {
          title: "ニュースレターの登録",
          condition: true,// フォームの表示
        });
    }
);
同じURLですが、「登録」ボタンが押されると、HTTPの「POST」メソッドを使ってバックエンドにデータが送られてくるのでその処理をする部分です。
app.post(
    CONSTANT.API_SIGNUP,
    (req: express.Request, res: express.Response) => {
        // フォームから送信されたE-Mailアドレスのデータ
        const email: string = req.body.email;
        // SendGridに送るデータの準備
        const options: any = {
          method: "POST",
          url: CONSTANT.SG_URL + CONSTANT.SG_API_CONTACTS,
          headers: { AUthorization: "Bearer " + CONSTANT.SG_API_KEY },
          data: [{ email: email }],
          json: true,
        };
        axios(options)
          .then((response: AxiosResponse) => {
            // 正常終了の場合は、functions/views/index.hbsのページを表示
            res.render("index", {
              title: "ニュースレター管理",
              condition: true,
            });
          })
          .catch((error) => {
              // エラーの場合、functions/views/signup.hbsのエラーメッセージ部分を表示
            res.render("signup", {
              title: "ニュースレターの登録",
              condition: false,
            });
          });
    }
);
このように、Webブラウザの表示要求と、Webブラウザがフォームのデータを送ってきた場合で別の処理をします。
最後にファンクションの為の記述を追加します。
exports.app = functions.https.onRequest(app);
これで、「app」という名前のファンクションとして利用できます。

Firebaseホスティングの設定

firebase.jsonを書き換えてFirebaseホスティングでファンクションを使えるようにします。
{
  "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/**"]
  }
}
以上です。以下のコマンドをfunctionsで実行すれば、標準設定の場合は、「http://localhost:5000」で開発用のサーバーにアクセスできます。
$ npm run build
$ firebase serve
プロジェクトフォルダの下の「public」は静的なファイルを置きます。今回はHTMLファイルを使用していないので、firebase initで作成された「index.html」は消去します。(バックエンドで「"/"」の処理をしない場合は問題ありませんが、単にWebサイトのトップのURLを指定した場合は、このフォルダにあるindex.htmlが表示されます。)
以上です。これで。全ての処理をバックエンドで行うことができるWebサイトができます。
まとめ
FirebaseとSendGridを利用した、メルマガ(ニュースレター)の実装の一部として、購読の登録をバックエンド処理で実装する方法を紹介してきました。
この実装例では、テンプレートエンジンの「express-handlebar」を利用して手軽にバックエンドでの実装を実現しています。
なお、この実装をインターネットで公開する場合、Firebaseのプランを従量制プラン(Blaze)にする必要があります。従量性プランでも無料枠内の仕様の場合は、無料で利用可能です。
サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す