Sails.js + MySQLでAPIバックエンドを作ってみる。

# これは何？
[Sail.js](https://sailsjs.com/)とMySQLを使ったAPIバックエンドのデモを作ってみたときの手順のメモです。
コード引用箇所は差分だけではなく、コメント文を削除した内容すべてを掲載しています。エラーハンドリングについては完璧ではありませんのでご了承ください。

# 手順

## 動作環境
- [Node.js](//nodejs.org): v16.9.1 
- [npm](https://www.npmjs.com): 7.21.1
- [Sails.js](//sailsjs.com): 1.5.0
- [MySQL](//www.mysql.com): 8.0


## Sails.jsの準備
### インストール
```shell
npm install -g sails
```

## Step1: DBへの接続をしない構成で動かしてみる
### Baseになるプロジェクトを作成する
```shell
$ sails new sails_sample
 Choose a template for your new Sails app:
 1. Web App  ·  Extensible project with auth, login, & password recovery
 2. Empty    ·  An empty Sails app, yours to configure
 (type "?" for help, or <CTRL+C> to cancel)

```

### HelloコントローラにindexというEndpointを作成する
```shell
$ sails generate controller hello index
 info: Created a new controller ("hello") at api/controllers/HelloController.js!
```

Routeを作成する。`config/routes.js` 以下のように修正する。

```bash
$ cat config/routes.js 
module.exports.routes = {
  '/': { view: 'pages/homepage' },
  'GET /api/v1/hello': { controller: 'HelloController', action: 'index' },
};
```

### 起動して表示してみる
```bash
$ sails inspect

# or 

$ sails lift
```
表示は以下の通り。ブラウザからのアクセスでも同様の出力が得られれば成功。

```bash
$ curl http://localhost:1337/api/v1/hello
{
  "todo": "index() is not implemented yet!"
}
```

## Step2: DBへの接続をし情報を取得してみる
### MySQLの準備
DB,テーブル、ユーザを作成

```bash
$ mysql -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.27 Homebrew

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> CREATE DATABASE sails_sample;
Query OK, 1 row affected (0.01 sec)

mysql> CREATE USER sails_sample_user_00@localhost;
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT ALL PRIVILEGES ON sails_sample.* TO sails_sample_user_00@localhost;
Query OK, 0 rows affected (0.01 sec)

mysql> ALTER USER 'sails_sample_user_00'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
Query OK, 0 rows affected (0.01 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE sails_sample.table00 (id int auto_increment PRIMARY KEY, message VARCHAR(300));
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO sails_sample.table00 (message) VALUES ('message1');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO sails_sample.table00 (message) VALUES ('message2');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO sails_sample.table00 (message) VALUES ('message3');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> \q
Bye
```

DBのテーブルの内容を確認する。

```bash
$ mysql -u sails_sample_user_00 sails_sample -ppassword
mysql: [Warning] Using a password on the command line interface can be insecure.
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.27 Homebrew

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT * FROM table00;
+----+----------+
| id | message  |
+----+----------+
|  1 | message1 |
|  2 | message2 |
|  3 | message3 |
+----+----------+

3 rows in set (0.00 sec)
```

### Sails.js側での設定

MySQLに接続する為のnpm packageをインストールする。

```bash
$ npm install -s sails-mysql
```

Model全体の設定をする。

```bash
$ cat config/models.js 
module.exports.models = {
  migrate: 'alter',
  attributes: {
    createdAt: { type: 'number', autoCreatedAt: true },
    updatedAt: { type: 'number', autoUpdatedAt: true },
    id: { type: 'number', autoIncrement: true, },
  dataEncryptionKeys: {
    default: 'toJwG8UaxxxxxxxxxxxxxxxxxlhY='
  },
  cascadeOnDestroy: true
};

```




DB接続を設定する。

```bash
$ cat config/datastores.js
module.exports.datastores = {
  default: {
    adapter: 'sails-mysql',
    url: 'mysql://sails_sample_user_00:password@localhost:3306/sails_sample',
  },
};
```

Modelを生成し、設定する。

```bash
$ sails generate model Table00
$ cat Table00.js
module.exports = {
  attributes: {
    message: 'string',
  },
};
```

Controllerを変更する。

```bash
$ cat api/controllers/HelloController.js
module.exports = {
  index: async function (req, res) {
    Table00.find().exec( function (err, records) {
      return res.json(records)
    })
  }
};
```

### 起動して表示してみる
```bash
# 起動していたら、Ctrl+cで停止する
$ sails inspect

# or 

$ sails lift
```
表示は以下の通り。ブラウザからのアクセスでも同様の出力が得られれば成功。

```bash
$ curl http://localhost:1337/api/v1/hello 
[
  {
    "id": 1,
    "message": "message1",
    "createdAt": "1634972725856",
    "updatedAt": "1634972725856"
  },
  {
    "id": 2,
    "message": "message2",
    "createdAt": "1634972727834",
    "updatedAt": "1634972727834"
  },
  {
    "id": 3,
    "message": "message3",
    "createdAt": "1634972729549",
    "updatedAt": "1634972729549"
  }
]
```

## Step3: DBへの接続をし情報投入、更新してみる
### 情報の投入

投入時のロジックをControllerに追加。

```bash
$ cat api/controllers/HelloController.js 
module.exports = {
  index: async function (req, res) {
    Table00.find().exec( function (err, records) {
      return res.json(records)
    })
  },

  postMessage: async function (req, res) {
    const ret = {
      success: false,
      message: null,
      data: null
    }
    try {
      if ( req.body.message!=null && typeof req.body.message=='string') {
        ret.data = await Table00.create({message:req.body.message}).fetch()
        ret.success = true
      }
      console.log(`parm=[${JSON.stringify(ret)}]`)
      return res.json(ret)
    } catch {
      return res.json(ret)
    }
  },
};

```

Routeを追加する。

```bash
$ cat config/routes.js 
module.exports.routes = {
  '/': { view: 'pages/homepage' },

  'GET /api/v1/hello': { controller: 'HelloController', action: 'index' },

  'POST /api/v1/hello/message': { controller: 'HelloController', action: 'postMessage' },
};
```

再起動してデータを投げてみて、追加されていたら完了。

```bash
$ curl -X POST -H "Content-Type: application/json" -d '{"message":"hoge4"}' localhost:1337/api/v1/hello/message
```


### 情報を更新

更新時の時のロジックをControllerに追加。

```bash
module.exports = {
  index: async function (req, res) {
    Table00.find().exec( function (err, records) {
      return res.json(records)
    })
  },

  postMessage: async function (req, res) {
    const ret = {
      success: false,
      message: null,
      data: null
    }
    try {
      if ( req.body.message!=null && typeof req.body.message=='string') {
        ret.data = await Table00.create({message:req.body.message}).fetch()
        ret.success = true
      }
      console.log(`parm=[${JSON.stringify(ret)}]`)
      return res.json(ret)
    } catch {
      return res.json(ret)
    }
  },

  patchMessage: async function (req, res) {
    const ret = {
      success: false,
      message: null,
      data: null
    }
    try {
      if ( (req.body.id!=null && req.body.message!=null)
        && ( typeof Number(req.body.id)=='number' && typeof req.body.message=='string')
      ) {
        ret.data = await Table00.updateOne({ id: Number(req.body.id) })
                                .set({ message:req.body.message });
        ret.success = true
      }
      console.log(`parm=[${JSON.stringify(ret)}]`)
      return res.json(ret)
    } catch {
      return res.json(ret)
    }
  },
};
```

Routeを追加する。

```bash
module.exports.routes = {
  '/': { view: 'pages/homepage' },

  'GET /api/v1/hello': { controller: 'HelloController', action: 'index' },

  'POST /api/v1/hello/message': { controller: 'HelloController', action: 'postMessage' },
  'PATCH /api/v1/hello/message': { controller: 'HelloController', action: 'patchMessage' },

};
```

再起動してデータを投げてみて、更新されていたら完了。

```bash
$ curl -X PATCH -H "Content-Type: application/json" -d '{id: 2, "message":"hogehoge2"}' localhost:1337/api/v1/hello/message
```




# Reference
- [Sails.js+MySQLで簡単なWEBアプリを作ってみる。 | キャスレーコンサルティング株式会社](https://www.casleyconsulting.co.jp/blog/engineer/225/)
- [RESTful API with sails js using MongoDB and MySQL](https://programmerblog.net/restful-api-with-sails-js/)
- [【node.js】MySQL8.0に接続できない。Error: ER_NOT_SUPPORTED_AUTH_MODE - 中堅プログラマーの備忘録](https://www.chuken-engineer.com/entry/2020/09/04/074216)