Vue.js + Express + Sequelize + DockerでCRUD搭載のTodoリストを作ってみる

# Vue.js + Express + Sequelize + DockerでCRUD搭載のTodoリストを作ってみる

## この記事に書いてあること
 - Expressの実装例
 - Vue.jsの実装例
 - 実行環境として利用するDockerコンテナの作り方
 - Sequelizeの導入方法、簡単な使い方
 - **拙い日本語**


しがないエンジニアが**人生初アドベントカレンダー参加**となりますので、諸々ご容赦頂けますと幸いです。

## 対象者
細かい説明は割愛していますが、初心者向けに書いてます。

「ゴリゴリにDocker使った環境構築が知りたい！」とか、
「俺はSequelizeなんか使わずにSQLを一つずつ組むね！」とか、
「ここどうなってるかもうちょい細かく説明してほしい」みたいな人にはあまり適してません。

あくまで、
**記事をなぞっていくだけで手軽にCRUDがVue.js＋Expressが体験できる**

っていうのを目的にしています。


## 各種バージョン

Docker for mac 19.03.5
Node.js 12.13.0
express 4.16.1
Sequelize-cli 5.5.1
vue-cli 4.0.5

DBはsqlite3を利用(なんでもいいけど)

## 作るもの
CRUD機能を搭載した簡単なTodoリストを作ります。
完成イメージはこんな感じです。


![cwljp-8g071.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/1986ba80-4814-f84d-8496-b28fb7c3e7e4.gif)

## 各種説明
### Docker
言わずと知れたコンテナマン。コンテナの概念やメリットの説明は割愛。今回環境はこれで構築。  
(ベストプラクティスを知りたい)

### Vue.js
みんな大好きフロントフレームワーク。**特に難しいことはしません。**
(vue-cliを使って雛形を作成)

### Node.js
みんな大好きサーバーサイドで動くJavaScript。**特に難しいことはしません。**

### Sequelize
Nodeで使えるORM。RailsのActiveRecordみたいな物だと思ってもらえればOK。
**特に難しいことはしません**

## 全体的なファイル構成

ファイル構成はこんな感じ。
rootディレクトリ配下をコンテナごとに区切り、後々コンテナにマウントしてあげる。

```
rootDir/
 ┣ docker-compose.yml
 ┣ vue/
 ┃  ┣ Dockerfile
 ┃  ┗ frontapp/
 ┃      ┗ Vue.jsの雛形ファイル群が入ってくる
 ┗ node/
    ┣ Dockerfile
    ┗ Expressの雛形ファイル群が入ってくる
```

## 全体構成
全体構成はこんな感じです。
基本Vue.jsコンテナがリクエストを受けて、axiosでNode.jsコンテナにサーバ通信しています。
SQLiteを使っているので、DBアクセスはNode.jsコンテナ内で処理されるようなイメージ。

![vuexpress.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/4bb732ba-121f-e0d2-3e10-9c8335704df6.png)


## Node.jsの準備
早速開始、と行きたいところですが、まずは実行環境を構築。
Dockerhubから引っ張ってきてもいいのですが、せっかくなのでDockerfileを書いてあげましょう。
今回はとりあえず各種プログラムが実行できる環境が前提なので、コンテナ内は環境のみ。
ソースファイルはコンテナイメージに含めず、docker-composeを使って後でマウントしてあげることにします。

```Dockerfile:node/Dockerfile
FROM node:12.13
RUN npm install -g express-generator sequelize-cli
```

Dockerfileの記述が終わったら一旦、Dockerfileからコンテナイメージを作成。
コンテナ起動時にローカルのディレクトリをマウントし、コンテナ内のデータが永続化できるようにします。

```shell
docker build node/. -t serverapp:latest
docker run -itd --rm --name serverapp -v $PWD/node:/node serverapp:latest
```

コンテナの起動が完了したら、コンテナ内にログインする。

```shell
docker exec -it serverapp /bin/bash
```

コンテナログイン後、expressコマンドを実行し、雛形ファイル群を作成。

```shell
cd /node
express .
npm install --save sequelize sqlite3 cors nodemon
npm install
```

`docker run`を実行した際にローカルのフォルダをマウントしているので、
ローカルの`node/`以下にexpressの雛形ファイル群ができているはず。

続いて、DBを作成するためにコンテナに入ったまま`sequelize init`を実行し、
CRUDに必要な`task`モデルを作成する準備をします。

```shell
sequelize init
```

init実行後、一旦コンテナからログアウトし、ローカルで`config/config.json`を下記のように修正。

```json:config/config.json
{
  "development": {
    "username": "root",
    "password": null,
    "database": "database_development",
    "host": "127.0.0.1",
    "dialect": "sqlite",
    "storage": "./data/development.sqlite3",
    "operatorsAliases": false
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "sqlite",
    "storage": "./data/test.sqlite3",
    "operatorsAliases": false
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "sqlite",
    "storage": "./data/production.sqlite3",
    "operatorsAliases": false
  }
}
```

Sequelizeのデータが保存される`./data`を作成する。

```shell
mkdir data
```

もう一度コンテナにログインし、`task`モデルを作成します。

```shell
docker exec -it serverapp /bin/bash
sequelize model:create --name task --underscored --attributes taskname:string
sequelize db:migrate
```
マイグレーションが無事に成功すればtaskモデルが作成されます。
これでNode.jsの準備は完了。雛形準備に利用したコンテナは停止します。

```shell
docker stop serverapp
```

続いてフロントVue.js側を準備します。

## Vue.jsの準備

Node側と同様にDockerfileを記述。

```Dockerfile:vue/Dockerfile
FROM node:12.13
RUN npm install -g @vue/cli
```

Dockerfileを元にコンテナイメージを作成。起動し、ローカルのフォルダをマウント。

```shell
docker build vue/. -t frontapp:latest
docker run -itd --rm --name frontapp -v $PWD/vue:/vue frontapp:latest
```

Node側同様、Vue.jsの雛形ファイル群を作成するため、一度コンテナ内にログインします。

```shell
docker exec -it frontapp /bin/bash
```

コンテナログイン後、下記コマンドを実行。オプションは `default` で問題ありません。
その後の選択肢としては、`yarn`と`npm`が出てくるけど、個人的にはnpmのが使いやすいのでそちらで。

```shell
cd /vue
vue create frontapp
```

以上でVue.js側の準備は終了。Node.js側同様に、一度コンテナは停止します。

```shell
docker stop frontapp
```

## docker-compose.ymlの準備

Node.jsとVue.jsそれぞれのコンテナを起動する際、composeファイルがあると起動/終了が楽なので、
`docker-compose.yml`を下記のように記入。

```yml:docker-compose.yml
version: "3"
services:
  node:
    build: node/.
    volumes:
      - ./node:/node
    working_dir: /node
    command: ["npm", "start"]
    ports:
      - "3000:3000"
  vue:
    build: vue/.
    volumes:
      - ./vue:/vue
    working_dir: /vue/frontapp
    command: ["npm", "run", "serve"]
    ports:
      - "8080:8080"
```
プロジェクトのカレントディレクトリで、`docker-compose`コマンドを実行し、
Node.jsのコンテナとVue.jsのコンテナを起動する。

```shell
docker-compose up -d
# コンテナ終了は docker-compose down
```

ブラウザで3000ポートにアクセスしExpressの画面、8080ポートにアクセスしVue.jsの画面が表示されれば、開発用コンテナの構築は完了です。

### localhost:3000
<img width="786" alt="スクリーンショット 2019-11-23 14.15.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/85604df8-fdc3-935e-e647-03d64f9d5d5d.png">

### localhost:8080
<img width="652" alt="スクリーンショット 2019-11-23 14.23.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/c8be3361-be99-5638-8b46-218dc148090c.png">


docker-composeコマンドで初回起動時にコンテナイメージが再ビルドされ、 `docker run` で起動した時のコンテナイメージとは別の名前でイメージ化されるため、 `docker run` コマンドで起動した時のコンテナイメージは不要のため削除します。


```shell
docker rmi serverapp:latest
docker rmi frontapp:latest
```

実行環境の作成はこれにて終了。次から各種機能の実装に入っていきます。

## Node.js

まずはサーバサイドの処理から実装します。
処理実装に入る前に、ソースコード変更時に自動的にNodeが再起動されるように少しだけ工夫。
`app.js`と`docker-compose.yml`を修正します。

```js:node/app.js
...
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
var cors = require("cors");

var app = express();

app.use(cors());
...
app.listen(3000, function() {
  console.log("Node server is started");
});

module.exports = app
```

```yml:docker-compose.yml
...
    working_dir: /node
    command: ["./node_modules/.bin/nodemon", "app"]
    ports:
...
```

Node.jsの初期状態だとVueからのリクエストを受けられない(エラーがでてしまう)ので、
インストールしたcorsモジュールを追加して、corsを許可する必要があります。
また、nodemonを利用しファイル変更を検知、ファイルが変更されたら自動でサーバが再起動されるようにします。
設定を反映するため、一度コンテナ再起動を実行。

```shell
docker-compose down
docker-compose up -d
```

コントローラーの新規追加がめんどくさいので最初から用意されている`index.js`を利用します。
実装内容としてはこんな感じ。

```js:node/routes/index.js
var express = require("express");
var router = express.Router();
const db = require("../models/index");

// Read
router.get("/", async function(req, res, next) {
  try {
    const result = await db.task.findAll({});
    res.send(result);
  } catch (err) {
    res.status(500).send(err);
  }
});

//Create
router.post("/task", async function(req, res, next) {
  try {
    const result = await db.task.create({
      taskname: req.body.task
    });
    res.send(result);
  } catch (err) {
    res.status(500).send(err);
  }
});

//Update
router.put("/task/:id", async function(req, res, next) {
  try {
    const result = await db.task.update(
      {
        taskname: req.body.task
      },
      {
        where: {
          id: req.params.id
        }
      }
    );
    res.send(result);
  } catch (err) {
    res.status(500).send(err);
  }
});

//Delete
router.delete("/task/:id", async function(req, res, next) {
  try {
    const result = await db.task.destroy({
      where: {
        id: req.params.id
      }
    });
    res.send({
      result: result
    });
  } catch (err) {
    res.status(500).send(err);
  }
});

module.exports = router;
```

送られてきたパラメータをそのままDBに登録するっていう簡単なCRUD処理の一覧です。
一つずつ説明していきます。

```js
// Read
router.get("/", async function(req, res, next) {
  try {
    const result = await db.task.findAll({});
    res.send(result);
  } catch (err) {
    res.status(500).send(err);
  }
});
```
getで`/`にアクセスされた際に、taskテーブルから全てデータを引っ張ってくるように実装。
DBのtaskテーブルから全てデータを引っ張ってくるのに、sequelizeの`findAll`というメソッドを使用します。
取得したデータを`res.send`を使用して、リクエスト元に戻してあげます。

```js
router.post("/task", async function(req, res, next) {
  try {
    const result = await db.task.create({
      taskname: req.body.task
    });
    res.send(result);
  } catch (err) {
    res.status(500).send(err);
  }
});
```
postで`/task`にアクセスした際に、リクエストのbody内容をDBのtaskテーブルに登録。
sequelizeの`create`というメソッドを利用します。

```js
router.put("/task/:id", async function(req, res, next) {
  try {
    const result = await db.task.update(
      {
        taskname: req.body.task
      },
      {
        where: {
          id: req.params.id
        }
      }
    );
    res.send(result);
  } catch (err) {
    res.status(500).send(err);
  }
});
```
putで`/task/:id`にアクセスした際に、idに紐づくレコードをupdateする処理。
sequelizeの`update`メソッドを利用し、リクエストのbody内容でデータを更新します。

```js
router.delete("/task/:id", async function(req, res, next) {
  try {
    const result = await db.task.destroy({
      where: {
        id: req.params.id
      }
    });
    res.send({
      result: result
    });
  } catch (err) {
    res.status(500).send(err);
  }
});
```
deleteで`/task/:id`にアクセスした際に、idに紐づくレコードを削除する処理。
sequelizeの`destroy`メソッドを利用。

Node.js側CRUDの処理は実装完了です。続いて、Vue.js側の実装。

## Vue.js

デフォルトでHelloWorld.vueというコンポーネントが存在しているので、そこに肉付けしていくことにします。
まずは各種部品を設置。

```vue:vue/frontapp/src/components/HelloWorld.vue
<template>
  <div class="hello">
    <form>
      <input type="text" style="display:none" />
      <input type="text" />
      <input type="button" value="add!" />
    </form>
    <table align="center" border="0">
      <tr>
        <th>task</th>
        <th>update</th>
        <th>delete</th>
      </tr>
      <tr>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="button" value="update" />
        </td>
        <td>
          <input type="button" value="delete" />
        </td>
      </tr>
    </table>
  </div>
</template>
<script>
export default {
  name: "HelloWorld"
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  margin: 0 10px;
}
a {
  color: #42b983;
}
.table {
  height: 100%;
  text-align: center;
}
</style>

```
上の基本系を修正します。今の時点だとこんな感じ。
![スクリーンショット 2019-11-23 10.12.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/576db9b5-dda1-1b72-81a0-991322313c12.png)



まずはGETで`/`にアクセスした際に、apiアクセスを行う部分を実装する。

```vue:vue/frontapp/src/components/HelloWorld.vue
<script>
import axios from "axios";
export default {
  name: "HelloWorld",
  data: () => ({
    tasks: [],
  }),
  created: async function() {
    try {
      const result = await axios.get("http://localhost:3000");
      this.tasks = result.data;
    } catch (err) {
      alert(JSON.stringify(err));
    }
  }
}
...
```

`created`を定義しておくことで、ページが読み込まれた時に処理を実行することが可能。
`created`でも`mounted`でもどっちでもいいけど、今回の用途には`created`の方が適任ですね。
下記記事で詳しく書かれていたので、気になる人は参照してみてください。

[Vuejs APIアクセスはcreatedとmountedのどちらで行う？](https://qiita.com/kimullaa/items/2bf8948dffb8c52d2b6b)

axiosは非同期で実行されるので、処理を同期的に行うためにawaitで実行。
実行結果として`result.data`が戻ってくるので、`data`内の`tasks`の内容をAPI実行結果に変更する。

続いてtask追加処理を実装。まず入力されたデータにアクセスできるようにする必要があるため、
テキストボックスをv-modelで`data`と紐づけます。

```vue:vue/frontapp/src/components/HelloWorld.vue
...
    <form>
      <input type="text" style="display:none" />
      <input v-model="currentTask" type="text" />
      <input type="button" value="add!" />
    </form>
...
<script>
import axios from "axios";
export default {
  name: "HelloWorld",
  data: () => ({
    tasks: [],
    currentTask: ""
  }),
...
```
こうすることで`input`に入力されたデータが、`data`内`currentTask`に反映され、関数内からデータが参照可能となります。
次に`@click`イベントを実装し、ボタンが押された時に関数を呼び出すよう修正。

```vue:vue/frontapp/src/components/HelloWorld.vue
...
    <form>
      <input type="text" style="display:none" />
      <input v-model="currentTask" type="text" />
      <input type="button" value="add!" @click="taskCreate" />
    </form>
...
<script>
...
  created: async function() {
    try {
      const result = await axios.get("http://localhost:3000");
      this.tasks = result.data;
    } catch (err) {
      alert(JSON.stringify(err));
    }
  },
  methods: {
    taskCreate: async function() {
      alert(this.currentTask);
    }
  }
};
</script>
```
こうすることで、ボタンが押された時に、`taskCreate`を呼び出すことが可能です。
仮実装としてボタンを押すと、テキストボックス内に入力したデータの内容をalert表示するように実装してます。
何かしらテキストボックスに入力し、ボタンを押したタイミングでalertが表示されてくればOK。

![スクリーンショット 2019-11-23 10.27.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/9f5a07f7-efb1-5dc3-8ddd-df218e9b4603.png)

取得したデータをサーバに送信するため、`taskCreate`関数の続きを実装していく。

```vue:vue/frontapp/src/components/HelloWorld.vue
<script>
...
methods: {
    taskCreate: async function() {
      try {
        const result = await axios.post("http://localhost:3000/task", {
          task: this.currentTask
        });
        this.tasks.push(result.data);
        this.currentTask = "";
      } catch (err) {
        alert(JSON.stringify(err));
      }
    }
...
```
node.js側で定義したタスク追加の処理(/task)に繋げる。
サーバ処理終了後に下記部分で動的に`tasks`にデータを追加。

```js
this.tasks.push(result.data);
// data内tasks配列に戻り値(追加したデータ)を追加
this.currentTask = "";
// data内currentTaskとテキストボックスが双方向でバインドされているので、currentTaskを空にすることでテキストボックスが空になる
```

これで、入力されたデータがサーバにリクエストで送られ、データ保存が可能となります。
このままだとデータを追加しても追加したデータが表示されないので、data内の`tasks`と `<tr>` を`v-model`を利用し紐づけます。
HelloWorld.vueの`table`部分を下記のように変更。

```vue:vue/frontapp/src/components/HelloWorld.vue
    <table align="center" border="0">
      <tr>
        <th>task</th>
        <th>update</th>
        <th>delete</th>
      </tr>
      <tr v-for="(task, index) in tasks" :key="task.id">
        <td>
          <input v-model="task.taskname" type="text" />
        </td>
        <td>
          <input type="button" value="update" />
        </td>
        <td>
          <input type="button" value="delete" />
        </td>
      </tr>
    </table>
```
`v-for`を利用することで`tasks`の数だけ `<tr>` が生成されます。
こうすることで、ページ読み込み時にサーバからデータが取得され、今まで追加したタスクが出てくるようになり、
ボタンを押した時にもタスクが画面に追加されるようになる。

![スクリーンショット 2019-11-23 10.44.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/f3a1aa4c-dc20-dec6-8fb3-c2617d108061.png)

これでタスク追加に関しては実装終了。続いてタスク削除を実装します。
まずは関数を用意。

```vue:vue/frontapp/src/components/HelloWorld.vue
<script>
...
  methods: {
    taskCreate: async function() {
      try {
        const result = await axios.post("http://localhost:3000/task", {
          task: this.currentTask
        });
        this.tasks.push(result.data);
        this.currentTask = "";
      } catch (err) {
        alert(JSON.stringify(err));
      }
    },
    taskDelete: async function(id, index) {
      try {
        await axios.delete("http://localhost:3000/task/" + id);
        this.currentTask = "";
        this.tasks.splice(index, 1);
      } catch (err) {
        alert(JSON.stringify(err));
      }
    }
  }
```
`taskDelete`関数を追加しました。taskのidと配列のindexを引数に持ってあげます。
呼び出し部分はこんな感じ。

```vue:vue/frontapp/src/components/HelloWorld.vue
      <tr v-for="(task, index) in tasks" :key="task.id">
        <td>
          <input v-model="task.taskname" type="text" />
        </td>
        <td>
          <input type="button" value="update" />
        </td>
        <td>
          <input type="button" value="delete" @click="taskDelete(task.id, index)" />
        </td>
      </tr>
```
こうすることで、リクエスト先のURLが動的に作られます。
タスクのidが`2`だった場合は、リクエスト先は`axios.delete("http://localhost:3000/task/" + 2);`となり、
タスクのidが`10`だった場合は、リクエスト先は`axios.delete("http://localhost:3000/task/" + 10);`となります。

また、配列のindexを引数に持ってあげることによって、spliceメソッドを使って配列を操作することができ、画面のデータを非同期で変更可能です。

```js
this.tasks.splice(index, 1);
// [{タスク1},{タスク2},{タスク3},{タスク4}]
// indexに2が渡された場合は、
// [{タスク1},{タスク2},{タスク4}]
// このように配列が操作される
```

最後にタスク修正した際の処理を実装する。まずは関数の用意から。

```vue:vue/frontapp/src/components/HelloWorld.vue
<script>
...
    taskUpdate: async function(id, val) {
      try {
        await axios.put("http://localhost:3000/task/" + id, {
          task: val
        });
        alert("タスクを修正しました");
        this.currentTask = "";
      } catch (err) {
        alert(JSON.stringify(err));
      }
    }
```

呼び出し部分はこんな感じ。

```vue:vue/frontapp/src/components/HelloWorld.vue
      <tr v-for="(task, index) in tasks" :key="task.id">
        <td>
          <input v-model="task.taskname" type="text" />
        </td>
        <td>
          <input
            type="button"
            value="update"
            @click="taskUpdate(task.id, task.taskname)"
          />
        </td>
        <td>
          <input
            type="button"
            value="delete"
            @click="taskDelete(task.id, index)"
          />
        </td>
      </tr>
```
`taskUpdate`を呼び出す時に、タスクのID、タスク名を引数で渡してあげることにより、
対象のタスクのみアップデートがかかるようにします。

CRUDの全体が出来上がった最終形のHelloWorld.vueとしてはこんな感じ。

```vue

<template>
  <div class="hello">
    <form>
      <input type="text" style="display:none" />
      <input v-model="currentTask" type="text" />
      <input type="button" value="add!" @click="taskCreate" />
    </form>
    <table align="center" border="0">
      <tr>
        <th>task</th>
        <th>update</th>
        <th>delete</th>
      </tr>
      <tr v-for="(task, index) in tasks" :key="task.id">
        <td>
          <input v-model="task.taskname" type="text" />
        </td>
        <td>
          <input
            type="button"
            value="update"
            @click="taskUpdate(task.id, task.taskname)"
          />
        </td>
        <td>
          <input
            type="button"
            value="delete"
            @click="taskDelete(task.id, index)"
          />
        </td>
      </tr>
    </table>
  </div>
</template>
<script>
import axios from "axios";
export default {
  name: "HelloWorld",
  data: () => ({
    tasks: [],
    currentTask: ""
  }),
  created: async function() {
    try {
      const result = await axios.get("http://localhost:3000");
      this.tasks = result.data;
    } catch (err) {
      alert(JSON.stringify(err));
    }
  },
  methods: {
    taskCreate: async function() {
      try {
        const result = await axios.post("http://localhost:3000/task", {
          task: this.currentTask
        });
        this.tasks.push(result.data);
        this.currentTask = "";
      } catch (err) {
        alert(JSON.stringify(err));
      }
    },
    taskDelete: async function(id, index) {
      try {
        await axios.delete("http://localhost:3000/task/" + id);
        this.currentTask = "";
        this.tasks.splice(index, 1);
      } catch (err) {
        alert(JSON.stringify(err));
      }
    },
    taskUpdate: async function(id, val) {
      try {
        await axios.put("http://localhost:3000/task/" + id, {
          task: val
        });
        alert("タスクを修正しました");
        this.currentTask = "";
      } catch (err) {
        alert(JSON.stringify(err));
      }
    }
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  margin: 0 10px;
}
a {
  color: #42b983;
}
.table {
  height: 100%;
  text-align: center;
}
</style>
```

以上で完成です！



## 作ってみた感想
Vue.jsのバインディングが思っていた以上に使いやすく、非常に簡単にフロントの実装ができました。
規模が大きくなってきたらVuexとかライフサイクルとか考えないといけないこともあるけど、
この程度の規模のアプリケーションであれば、簡単に作ることができるのが良いですね。

あと、ただ実装するだけだと面白く無いので実行環境はDockerにしたのも◯。
汎用的な実行コンテナとして使えるので、チュートリアル的な使い方には非常に適任。
構築も簡単なので非常に使いやすかったかと。

Sequelizeの導入がちょっとだけめんどくさいけど、一度やってしまえばRailsチックにDB操作ができるので、
SQL書かずに手軽にDB操作したい！って人にはおすすめです。

明日は@kazukimatsumotoさんの番です。よろしくおねがいします！