【Firebase】Cloud Functions + Express + EJSで動的コンテンツを配信する

Firebaseの公式リファレンスを見ると以下のように書いてありました。

[Cloud Functions による動的コンテンツの配信](https://firebase.google.com/docs/hosting/functions?hl=ja)
> *Firebase Hostingでは、Cloud Functionsを使用してサーバー側の処理を実行できます。つまり、Firebase Hostingサイトのコンテンツの動的生成をサポートすることができます。*

どうやらFirebase Hostingは、動的なサイトも運用できるという、何とも素晴らしいサービスのようなので、実際に動的コンテンツの配信を行ってみました。

Node.jsの「Express」を使います。テンプレートエンジンには「EJS」を使います。（ここは別に「Pug」でも「handlebars」でも問題ないです）

Firebase Hostingの環境構築の説明は割愛します。
詳しくは[こちら](https://firebase.google.com/docs/hosting/quickstart?hl=ja)をどうぞ


# プロジェクトの階層
階層はこんな感じです。

```
├── firebase.json
├── functions
│   ├── firebase-debug.log
│   ├── index.js
│   ├── node_modules
│   │   ├── @types
│   │   ├── ・・・・・・・
│ 　　　　│ 　　　　├── ・・・・・・・
│   ├── package-lock.json
│   ├── package.json
│   └── views
│       └── template.ejs
└── public
    └── index.html
```

#npmで「Express」と「EJS」をインストール

expressのインストール

```
$ npm install express --save
```

ejsのインストール

```
$ npm install ejs --save
```
それぞれnpmでインストールする必要があります。

# firebase.json ファイル

`firebase.json` を開き、内容を次のコードに置き換えます。

```json:firebase.json
{
  "hosting": {
    "public": "public",
    "rewrites": [
       {
         "source": "/**",
         "function": "sample"
       }
    ]
  }
}
```

### 関数にHostingリクエストを送信する

リライトルールを使用すると、特定のパターンに一致するリクエストを単一の宛先に送ることができます。
`/**` のすべてのリクエストを送信して `sample` というHTTPS関数を実行します。

今回はトップページは静的なページを表示し、 `/**` にアクセスがあると（/page1と/page2）、 `**` に応じた動的コンテンツを返します。

### public
ルート直下 `/` は「public」ディレクトリ内の `index.html` ファイルがロードされます。
ファイルの中身は以下です。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta http-equiv="content-type">
    <title>トップページ</title>
    <style>
    h1 { font-size:18px; background-color:#eaeaea; }
    </style>
</head>
<body>
    <header>
        <h1 id="h1">トップページ</h1>
    </header>
    <div>
        <p>これはトップページです。</p>
        <ul>
          <li><a href="/page1">ページ1へ</a></li>
          <li><a href="/page2">ページ2へ</a></li>
        </ul>
    </div>
</body>
</html>
```

# HTTPS関数を作成する index.js

`/functions/index.js` を開き、内容を次のコードに置き換えます。前述した `sample` というHTTPS 関数をここで作成。

```js:index.js
const functions = require('firebase-functions');
const express = require('express');
const url = require('url');
const app = express();

const routes = {
    "/page1":{
        "title":"ページ1",
        "content":"これはページ1です。"
    },
    "/page2":{
        "title":"ページ2",
        "content":"これはページ2です。"
    }
};

app.set("view engine","ejs");
app.engine('ejs', require('ejs').__express);

app.get("*",function(req,res){

  const url_parts = url.parse(req.path);
  
  if(routes[url_parts.pathname] == null){
    res.status(404).send("page not found");
  }else{
    res.status(200).render("template",
      {
          title: routes[url_parts.pathname].title,
          content: routes[url_parts.pathname].content
      }
    );
  }
});

exports.sample = functions.https.onRequest(app);
```

### ビューエンジンにejsをセットする
以下の部分です。

```js:
app.set("view engine","ejs");
app.engine('ejs', require('ejs').__express);
```

### routesにデータをまとめておく
各ページのデータを `routes` 変数にまとめています。
アクセス先のpathをキーとして用意し、そのpathで表示されるページ情報を連想配列で値にセットします。

この `routes` 変数から、パスごとに必要な情報をロードして処理します。

```js:
const routes = {
    "/page1":{
        "title":"ページ1",
        "content":"これはページ1です。"
    },
    "/page2":{
        "title":"ページ2",
        "content":"これはページ2です。"
    }
};
```

### 「url」オブジェクトをロードする
「url」オブジェクトをロードします。URLの文字列をパースする為に使います。

```js:
const url = require('url');
```

### アクセスしてきたURLをパースする
パースして `url_parts` 変数に変換します。

```js:
const url_parts = url.parse(req.path);
```

### 判定

アクセスがあったpathを判定して、 `routes[url_parts.pathname]` に一致しなければ404を送り、一致すれば、 `routes` 変数を元にしたデータをJSONで送り、テンプレートをレンダリングします。

```js:
if(routes[url_parts.pathname] == null){
    res.status(404).send("page not found");
}else{
    res.status(200).render("template",
      {
          title: routes[url_parts.pathname].title,
          content: routes[url_parts.pathname].content
      }
    );
}
```

# テンプレートファイルを用意

「functions」ディレクトリ内に「views」ディレクトリを作成し、その中に `template.ejs` ファイルを作成します。ファイルの中身は以下です。

```html+erb:template.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta http-equiv="content-type">
    <title><%=title %></title>
    <style>
    h1 { font-size:18px; background-color:#eaeaea; }
    </style>
</head>
<body>
    <header>
        <h1 id="h1"><%=title %></h1>
    </header>
    <div>
        <p><%-content %></p>
        <ul>
          <li><a href="/">トップページへ</a></li>
        </ul>
    </div>
</body>
</html>
```

# デプロイ後の結果

`/` は静的なトップページが表示され、`/page1` `/page2`にアクセスすると動的ページとしてそれぞれのURLに応じたコンテンツが表示されます。

![スクリーンショット_2017-10-07_21_16_48.jpg](https://qiita-image-store.s3.amazonaws.com/0/67591/2da59d3b-00c0-f7a6-811b-fc4c268c01e6.jpeg)


![スクリーンショット_2017-10-07_21_17_08.jpg](https://qiita-image-store.s3.amazonaws.com/0/67591/98265b45-0448-ffde-ff2d-482b2490e9a6.jpeg)

# まとめ
Firebase　HostingでCloud Functionsを使えば本格的に動的なサイトも運用できるんです。
しかも**無料で**（制限あり）

Firebaseは制限多いと思ってましたけど、これならAWS Lambdaより気軽に柔軟なアプリケーションが作れる気がします。Firebase Realtime Databaseと組み合わせて。