CloudFunctionsからCloudSQLに接続

福岡から世界中の"むずかしい"を簡単にする株式会社[diffeasy](https://diffeasy.com)CTOの西[@_takeshi_24](https://twitter.com/_takeshi_24)です。

この記事はアドベントカレンダー「[diffeasyCTO西の24(にし)日連続投稿チャレンジ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/diffeasy24)」の7日目の記事です。

この記事は「Nuxt.jsとFirebaseとCloudFunctionsでWebアプリ開発」シリーズとして、連載していきます。
Nuxt.jsとFirebaseなどを使ってWebアプリケーション開発にチャレンジしたい方、是非Qiitaアカウントか[twitterをフォロー](https://twitter.com/_takeshi_24)していただき、ツッコミやいいね！お願いします！

ここまでの記事では、Firebaseの機能について書いてきましたが、Firebaseで用意されているデータベースはNoSQLのみです。
NoSQLは非常に便利ですが、やはり複雑なデータを取得する必要があるケースはRDBの方が操作しやすい場合が多いです。（私がRDB脳だからかもしれませんが・・・。）

そこで、CloudFunctionsからCloudSQLのRDBに接続する方法についてまとめます。

## はじめに
こちらの記事は、「Nuxt.jsとFirebaseとCloudFunctionsでWebアプリ開発」シリーズとして連載していますので、「[diffeasyCTO西の24(にし)日連続投稿チャレンジ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/diffeasy24)」の過去の記事もご覧ください。

##CloudSQLの準備
FireStoreのCloudFunctionsはGCPの管理コンソール画面から見ても、同じようにデプロイされています。
同じアカウントでGCPのコンソールにログインしてください。

1.メニューからCloudSQLを開き、「インスタンスを作成」をクリックします。
<img width="1202" alt="スクリーンショット 2019-11-30 22.50.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/7d8bac45-2552-b5be-11fd-964b89d5350a.png">

2.DBを選択します。今回はMySQLを選択します。
<img width="800" alt="スクリーンショット 2019-11-30 22.55.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/60331b52-01bd-daf1-0836-bf9456446299.png">

3.DB名、パスワードなど必要な事項を入力し、DBインスタンスを作成します。
<img width="578" alt="スクリーンショット 2019-11-30 23.00.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/abc5c499-2e45-d4b7-c39a-fba6657d2da2.png">

4.しばらく待つと、DBインスタンスが起動します。

##ローカルからCloudSQLに接続する
テスト用のテーブルを作成するため、ローカルからCloudSQLに接続します。
IPとパスワードによる認証ではなく、よりセキュアなCloudProxy経由で接続します。

CloudProxyについては、[こちら](https://cloud.google.com/sql/docs/mysql/connect-admin-proxy?hl=ja#install)を参考にしてください。

ここでは、MacOSでの手順を書きます。
1.CloudProxyをインストールする。

```terminal
$ curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64
$ chmod +x cloud_sql_proxy
```
2.GCP管理コンソールの「IAMと管理」→「サービスコンソール」を開きます。

3.サービスアカウト名を入力します。
<img width="1026" alt="スクリーンショット 2019-11-30 23.31.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/7a0fc24e-e56a-8c7c-c875-7e54af653d4a.png">

４.CloudSQL管理者の役割を選択します。
<img width="1115" alt="スクリーンショット 2019-11-30 23.27.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/bf85662d-34b4-5bf0-5c60-d678f4d70ab1.png">

5.「キーを作成」をクリックし、JSON形式でダウンロードします。

6.CloudSQL Proxyを使ってDBインスタンスと接続します。

```
./cloud_sql_proxy -instances=<INSTANCE_CONNECTION_NAME>=tcp:3306 \
                  -credential_file=<PATH_TO_KEY_FILE> &
```

```Cloud SQL Admin API has not been used in project```のエラーが出た場合は、エラーメッセージに表示されるリンクを開いて、「CloudSQL Admin API」を有効にして再度接続してください。

```
2019/11/30 23:36:16 Listening on 127.0.0.1:3306 for sample-xxxxx:asia-northeast1:sample
2019/11/30 23:36:16 Ready for new connections
```
のようなメッセージが表示され、```127.0.0.1:3306```で接続されます。

7.```mysql -uroot -p --host 127.0.0.1```でMySQLに接続します。

8.sampleDBを作成します。

```sql
CREATE DATABASE sample DEFAULT CHARACTER SET utf8mb4;
```

9.sampleDBに接続します。

```sql
CONNECT sample;
```

10.usersテーブルを作成します。

```sql
CREATE TABLE users(
    id INT(11) AUTO_INCREMENT NOT NULL, 
    firstName VARCHAR(30) NOT NULL,
    familyName VARCHAR(30) NOT NULL,
    PRIMARY KEY (id)
);
```

##CloudFunctionsからCloudSQLのRDBに接続
1.configにDB接続情報を設定します。

```terminal
firebase functions:config:set db.name="sample" db.user="root" db.password="password" db.connection="sample-xxxxx:asia-northeast1:sample"
```

2.ローカル開発のため、```/functions/.runtimeconfig.json```にconfigをエクスポートします。

```terminal
firebase functions:config:get > .runtimeconfig.json
```

3.CloudFunctionsでは、接続名で接続し、ローカル環境では、CloudSQL Proxy経由で、```127.0.0.1```のhostで接続しますので、```.runtimeconfig.json```に以下の設定を追加します。

```json:.runtimeconfig.json
{
  "db": {

    "host": "127.0.0.1"
  }
}
```

4.Yarnでmysqlパッケージを追加します。

```
$ yarn add mysql
```

5.```/functions/index.js```に以下の内容を追加します。

```javascript:/functions/index.js
const functions = require("firebase-functions");
const https = functions.region("asia-northeast1").https;
const admin = require("firebase-admin");
admin.initializeApp();
const mysql = require("mysql");
const util = require("util");

const mysqlConfig = {
  connectionLimit: 1,
  user: functions.config().db.user,
  password: functions.config().db.password,
  database: functions.config().db.name
};
let mysqlPool;

exports.updateUser = https.onCall(async (data, context) => {
  let user = await _selectUser(context.auth.uid);
  if (user) {
    user.firstName = data.firstName;
    user.familyName = data.familyName;
    return await _updateUser(user);
  } else {
    user = {
      uid: context.auth.uid,
      firstName: data.firstName,
      familyName: data.familyName
    };
    return await _insertUser(user);
  }
});

exports.getUser = https.onCall(async (data, context) => {
  return await _selectUser(context.auth.uid);
});

// usersをSELECT
_selectUser = async function(uid) {
  await _initMysqlPool();
  const query = `select * from users where uid = ?`;

  let record = await mysqlPool.query(query, uid);

  console.log(record);
  let user = null;
  if (record.length > 0) {
    user = record[0];
  }

  return user;
};

// usersにINSERT
_insertUser = async function(user) {
  await _initMysqlPool();
  const query = `insert into users (uid, firstName, familyName) values (?, ?, ?)`;

  let record = await mysqlPool.query(query, [
    user.uid,
    user.firstName,
    user.familyName
  ]);

  return record;
};

// usersを更新
_updateUser = async function(user) {
  await _initMysqlPool();
  const query = `update users set firstName = ? , familyName = ? where uid = ? `;

  let record = await mysqlPool.query(query, [
    user.firstName,
    user.familyName,
    user.uid
  ]);

  return record;
};

// ConnectionPoolを初期化し、Promise化
_initMysqlPool = async function() {
  if (functions.config().db.host) {
    // ローカルは、host名でlocalのCloudSQL Proxyに接続
    mysqlConfig.host = functions.config().db.host;
  } else {
    // CloudFunctionsは、接続名で接続
    mysqlConfig.socketPath = `/cloudsql/${functions.config().db.connection}`;
  }

  if (!mysqlPool) {
    mysqlPool = await mysql.createPool(mysqlConfig);
  }

  // promisifyで、Promise化
  mysqlPool.query = util.promisify(mysqlPool.query);
};
```



##Nuxt.jsからCloudSQLを扱うCloudFunctionsに接続。
1.```/pages/mysql_user/index.vue```を以下の内容で新規作成。

```vuejs:/pages/mysql_user/index.vue
<template>
  <section class="container">
    <v-row>
      <v-col>
        <v-text-field v-model="familyName" placeholder="姓"></v-text-field>
      </v-col>
      <v-col>
        <v-text-field v-model="firstName" placeholder="名"></v-text-field>
      </v-col>
      <v-col>
        <v-btn @click="insertUser">更新</v-btn>
      </v-col>
    </v-row>
  </section>
</template>
<script>
import { functions } from "~/plugins/firebase";

export default {
  data() {
    return {
      firstName: "",
      familyName: ""
    };
  },
  mounted() {
    this.getUser();
  },
  methods: {
    async getUser() {
      const getUser = functions.httpsCallable("getUser");
      const result = await getUser();
      if (result.data) {
        this.firstName = result.data.firstName;
        this.familyName = result.data.familyName;
      }
    },
    async insertUser() {
      const updateUser = functions.httpsCallable("updateUser");
      await updateUser({
        firstName: this.firstName,
        familyName: this.familyName
      });
    }
  }
};
</script>
```

2.ローカルで、```yarn run dev```で起動して、http://localhost:3000/mysql_user/ に接続して、以下の画面が表示され、名前が更新されたら成功です。
<img width="894" alt="スクリーンショット 2019-12-01 12.08.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/d85a93f6-b6eb-a893-da89-8584b1712e7a.png">

##本番環境にデプロイして確認
1.nuxt-sampleをgenerateします。

```terminal
$ cd nuxt-sample
$ yarn generate:prod
```

2.```firebase.json```ファイルと同じディレクトリで、デプロイコマンドを実行します。

```terminal
$ firebase deploy
```

3.ローカル環境と同じように動作確認できれば、成功です！

##最後に
この記事は「Nuxt.jsとFirebaseとCloudFunctionsでWebアプリ開発」シリーズとして、連載していきます。
続きはアドベントカレンダー「[diffeasyCTO西の24(にし)日連続投稿チャレンジ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/diffeasy24)」に掲載していきます。

Nuxt.jsとFirebaseなどを使ってWebアプリケーション開発にチャレンジしたい方、是非Qiitaアカウントか[twitterをフォロー](https://twitter.com/_takeshi_24)していただき、ツッコミやいいね！お願いします！

[#advent_24](https://twitter.com/hashtag/advent_24)のハッシュタグでフィードバックいただけると嬉しいです！