Vue.js + Firebase functionsでお問い合わせフォームを作成する

###2021/01/08 追記
**Firebase Trigger Emailを使う方法で書き直しました。**
[Firebase Trigger Email で作る SPA サイトのお問い合わせフォーム](https://zenn.dev/ryo_kawamata/articles/firebase-trigger-email)



Firebase hosting + Vue.jsでコーポレートサイトを作成する際に困るのが、お問い合わせフォームをどうするかだと思います。
Googleフォームやフォームランなどのフォーム作成サービスを設置するしかないかと思っていたのですが、Firebaseのfunctionsを利用することで、かなり簡単に実装できました。
Fireabse Hostingでホストして、Functionsでメール送信機能を実装するまでをチュートリアル形式で記載します。

以下のようなフォームを作成します。

![Jan-05-2019 13-11-02.gif](https://qiita-image-store.s3.amazonaws.com/0/114396/43976125-339d-5adc-103a-01814c7ecb8b.gif)

またここで説明するコードは以下Githubリポジトリで確認できます。

https://github.com/kawamataryo/firebase-send-mail-demo


# 1. プロジェクトの準備
### 1-1. Vueプロジェクトの作成
元なるvueプロジェクトを作成します。選択はデフォルトでOKです。

```bash
$ vue create sendmail-demo
```

以下コマンドでlocalhost:8080にアクセスして初期画面が表示されれば準備完了です。

```bash
$ cd sendmail-demo
$ yarn serve
```

<img width="859" alt="スクリーンショット 2019-01-05 13.22.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/114396/eeebee34-74aa-3f38-c4be-ce183f506b3e.png">

### 1-2. Firebaseの設定
事前に[こちら](https://firebase.google.com/docs/cli/)を参考にFirebaseCLIのインストールを行ってください。

Firebase cliでFirebaseの設定を行います。
選択肢ではFunctionsとHostingにチェックを入れます。

```bash
$ firebase init
     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/kawamataryou/firebase_training/sendmail-demo

? Which Firebase CLI features do you want to setup for this folder? Press Space to select features, then Enter to confirm your choices.
 ◯ Database: Deploy Firebase Realtime Database Rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
 ◉ Functions: Configure and deploy Cloud Functions
❯◉ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Select a default Firebase project for this directory: [don't setup a default project]
```

Functionsの設定はデフォルトでOKです。

```bash
=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
? File functions/package.json already exists. Overwrite? No
i  Skipping write of functions/package.json
? File functions/index.js already exists. Overwrite? No
i  Skipping write of functions/index.js
? Do you want to install dependencies with npm now? Yes
audited 4168 packages in 3.772s
found 0 vulnerabilities
```

Hostingの選択肢では、dist, yesを入力してください。

```bash
=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? dist
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
✔  Wrote dist/index.html

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!
```

### 1-3. Firebaseコンソールでのプロジェクト作成、設定
プラウザで[Fireabseコンソール](https://console.firebase.google.com/u/0/)にアクセスしてプロジェクトを作成します。

作成後,先程作ったsendmail-demoにプロジェクトを適応します。
これで下準備は整いました。

```bash
$ firebase use --add send-mail-demo
```

# 2. Firebase functionでのメール通知処理の実装
### 2-1 nodemailerの追加
functinonsでのメール通知を実現するため、node.jsからメール送信を可能にするモジュールnodemailerを追加します。

```bash
# 1でfirebase functionsの設定を行うとfunctionsディレクトリが自動で作られます。
$ cd functions
$ yarn add nodemailer
```

### 2-2. 設定ファイルの追加
今回は送信サーバーとしてgmailを使うので、そのログイン情報、パスワード及び、ページ管理者（問い合わせメールの送信先）の情報を
functionsの環境変変数に追加します

```bash
$ firebase functions:config:set gmail.email="メールサーバーとして使うgmailのログインID" gmail.password="メールサーバーとして使うgmailのパスワード" admin.email="問い合わせメールの送信先となるページ管理者のアドレス"
```

functions内では、以下構文でこれらの環境変数にアクセスできます。

```js
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
const adminEmail = functions.config().admin.email;
```


### 2-3. functionの作成
そしてfunctionsのindex.jsに以下を記述し、sendmailというfunctionを作成します。

```bash
# functionsディレクトリにて
$ vi index.js
```

```js
const functions = require("firebase-functions");
const nodemailer = require("nodemailer");
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
const adminEmail = functions.config().admin.email;

// 送信に使用するメールサーバーの設定
const mailTransport = nodemailer.createTransport({
  service: "gmail",
  auth: {
    user: gmailEmail,
    pass: gmailPassword
  }
});

// 管理者用のメールテンプレート
const adminContents = data => {
  return `以下内容でホームページよりお問い合わせを受けました。

お名前：
${data.name}

メールアドレス：
${data.email}

内容：
${data.contents}
`;
};

exports.sendMail = functions.https.onCall(async (data, context) => {
  // メール設定
  let adminMail = {
    from: gmailEmail,
    to: adminEmail,
    subject: "ホームページお問い合わせ",
    text: adminContents(data)
  };

  // 管理者へのメール送信
  try {
    await mailTransport.sendMail(adminMail);
   } catch (e) {
    console.error(`send failed. ${e}`);
    throw new functions.https.HttpsError('internal', 'send failed');
   }
  }
});
```

これでfunctionsの設定は完了です。


# 3. Vue.jsでFormの作成
最後にクライアント側のForm作成を行います。

### 3-1 fireabse, vuetifyのモジュールを追加
フォーム作成で楽をするために、コンポーネントライブラリの[Vuetify](https://vuetifyjs.com/en/)を使用します。

```bash
# プロジェクト直下で。選択肢はデフォルトでOK。
$ vue add vuetify
```

次にFirebase functionsのクライアントとして使用するため、firebaseのモジュールを追加します。

```
# プロジェクト直下で
$ yarn add firebase
```

### 3-2. Firebaseの初期設定
pluginsディレクトリにFireabaseの初期化用のjsファイルを追加します。

```bash
$ vi src/plugins/firebase.js
```

config内の値は、Firebaseのプロジェクトコンソールにて、確認してください。

```js
import firebase from "firebase";

const config = {
  apiKey: "xxxxxx",
  authDomain: "xxxxxx.firebaseapp.com",
  databaseURL: "xxxxxx.firebaseio.com",
  projectId: "xxxxxx",
  storageBucket: "xxxxxx.appspot.com",
  messagingSenderId: "xxxxxx"
};
firebase.initializeApp(config);
export const functions = firebase.functions();
```

### 3-3. Formコンポーネントの作成
components配下でformのコンポーネントを作成します。

```bash
$ vi src/components/ContactForm.vue
```

そして、以下内容を記載します。

```vue:ContactForm.vue
<template>
    <div>
        <v-card>
            <v-container>
                <h2>お問い合わせ</h2>
                <v-form ref="form" v-model="contactFormValidation.valid" lazy-validation>
                    <v-text-field
                            v-model="contactForm.name"
                            :rules="contactFormValidation.nameRules"
                            label="名前"
                            required
                    ></v-text-field>
                    <v-text-field
                            v-model="contactForm.email"
                            :rules="contactFormValidation.emailRules"
                            label="メールアドレス"
                            required
                    ></v-text-field>
                    <v-textarea
                            v-model="contactForm.contents"
                            :rules="contactFormValidation.contentsRules"
                            label="内容"
                            required
                    ></v-textarea>
                    <v-btn
                            :loading="contactForm.loading"
                            :disabled="!contactFormValidation.valid"
                            @click="sendMail()"
                            block
                            large
                            color="info"
                            class="mt-4 font-weight-bold"
                    >送信
                    </v-btn>
                </v-form>
            </v-container>
        </v-card>
        <v-snackbar
                v-model="snackBar.show"
                :color="snackBar.color"
                bottom
                right
                :timeout="6000"
                class="font-weight-bold"
        >
            {{snackBar.message}}
        </v-snackbar>
    </div>
</template>

<script>
  import { functions } from '@/plugins/firebase'

  export default {
    data: () => ({
      contactForm: {
        name: '',
        contents: '',
        email: '',
        loading: false
      },
      contactFormValidation: {
        valid: false,
        nameRules: [v => !!v || '名前は必須項目です'],
        emailRules: [v => !!v || 'メールアドレスは必須項目です'],
        contentsRules: [v => !!v || '内容は必須項目です']
      },
      snackBar: {
        show: false,
        color: '',
        message: ''
      }
    }),
    methods: {
      sendMail: function () {
        if (this.$refs.form.validate()) {
          this.contactForm.loading = true
          const mailer = functions.httpsCallable('sendMail')

          mailer(this.contactForm)
            .then(() => {
              this.formReset()
              this.showSnackBar(
                'success',
                'お問い合わせありがとうございます。送信完了しました'
              )
            })
            .catch(err => {
              this.showSnackBar(
                'error',
                '送信に失敗しました。時間をおいて再度お試しください'
              )
              console.log(err)
            })
            .finally(() => {
              this.contactForm.loading = false
            })
        }
      },
      showSnackBar: function (color, message) {
        this.snackBar.message = message
        this.snackBar.color = color
        this.snackBar.show = true
      },
      formReset: function () {
        this.$refs.form.reset()
      }
    }
  }
</script>
```

次に、App.vueでContactForm.vueを読み込むように設定します。

```bash
vi src/App.vue
```

```vue:src/App.vue
<template>
  <v-app>
    <v-toolbar app>
      <v-toolbar-title class="headline text-uppercase">
        <span>Vuetify</span>
        <span class="font-weight-light">MATERIAL DESIGN</span>
      </v-toolbar-title>
      <v-spacer></v-spacer>
      <v-btn
        flat
        href="https://github.com/vuetifyjs/vuetify/releases/latest"
        target="_blank"
      >
        <span class="mr-2">Latest Release</span>
      </v-btn>
    </v-toolbar>

    <v-content>
      <ContactForm/>
    </v-content>
  </v-app>
</template>

<script>
import ContactForm from './components/ContactForm'

export default {
  name: 'App',
  components: {
    ContactForm
  },
  data () {
    return {
      //
    }
  }
}
</script>
```

これでクライアント側の設定は完了です。

# 4. メールサーバーの設定・デプロイ
### 4-1. gmailの設定
ここまででいざデプロイと行きたいところなのですが、gmailをメールサーバーとして使用する場合、安全性の低いアプリのアクセスを有効にする必要があります。

メールサーバーとして利用するGoogleアカウントにログイン後、以下URLにアクセスして、設定を有効にしてください。
https://myaccount.google.com/lesssecureapps

<img width="500" alt="gmail.png" src="https://qiita-image-store.s3.amazonaws.com/0/114396/21711cc6-86cc-a005-abd7-0388f3d74c09.png">





### 4-2. Vueプロジェクトのbuild
Vueプロジェクトのbuildを行います。実行するとdistディレクトリに実行ファイルが作成されます。

```bash
$ yarn run build
```


### デプロイ
いよいよ最後。
以下コマンドを実行してDeploy complete!が表示されればデプロイ完了です。
Hosting URL:xxxxにアクセスして実際にフォームに送信してみてください。

```bash
$ firebase deploy

=== Deploying to 'send-mail-demo'...

i  deploying functions, hosting
i  functions: ensuring necessary APIs are enabled...
⚠  functions: missing necessary APIs. Enabling now...
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (72.49 KB) for uploading
✔  functions: functions folder uploaded successfully
i  hosting[send-mail-demo]: beginning deploy...
i  hosting[send-mail-demo]: found 1 files in dist
✔  hosting[send-mail-demo]: file upload complete
i  functions: creating Node.js 6 function sendMail(us-central1)...
✔  functions[sendMail(us-central1)]: Successful create operation.
Function URL (sendMail): https://us-central1-xxxx.cloudfunctions.net/sendMail
i  hosting[send-mail-demo]: finalizing version...
✔  hosting[send-mail-demo]: version finalized
i  hosting[send-mail-demo]: releasing new version...
✔  hosting[send-mail-demo]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/send-mail-demo/overview
Hosting URL: https://xxxx.firebaseapp.com
```


### 感想
Firebase本当にすごいですね。ここまで全て無料です。
今後もFireabse Vue.jsで色々作っていきたいです。

# 参考
* [VuejsとFirebaseでメール送信機能を実装する](https://qiita.com/Hiroyuki1993/items/1ab9266ca6fc422113e3)