React+Node.js(Express)でMySQL連携する

##### はじめに
Reactの学習をはじめようと思った時に、DBとの連携はどのようにするのか不明だったので調査したところ、「Node.jsでAPIサーバーを立てて、Reactから実行、結果をごにょごにょする」という結論に至ったのでそれを実現するための備忘録です。

##### 開発環境
・OS: MacOS Big Sur
・Editor: Visual Studio Code
・DB: MySQL

##### 事前準備
Node.jsやReact、MySQL Serverのインストールは事前に済んでいるものとします。
DB、テーブルは事前に簡単なものを作成済みです。
***
VSCodeのターミナルで作業を行います。
clientディレクトリとserverディレクトリを作成
```
$ mkdir client
$ mkdir server
```
#### Reactの設定
client側にはReactをインストールする
```
$ cd client/
$ create-react-app .
```
![スクリーンショット 2022-04-10 1.01.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/5cd989a0-4f8a-7b5d-424e-93beef00ce47.png)
Reactのインストールが完了
![スクリーンショット 2022-04-10 1.15.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/314d8f98-48fb-ec82-3d4f-a9ab7f9b4558.png)
モジュールを一つインストールしておく
```
$ npm install axios

axios: 特定のエンドポイントへのリクエストを送信できるようにする、HTTPクライアント
```
起動
```
$ npm start
```
ブラウザが自動で立ち上がります
![スクリーンショット 2022-04-10 1.25.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/e12cd1ef-597a-d864-4ac9-2cd32a5c1e80.png)
以下のファイルは使わないので削除します。
・App.test.js
・index.css
・logo.svg
・setupTests.js

また、残した以下のファイルで不要な記述は削除します。
```javascript:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
    <App />,
  document.getElementById('root')
);
```
```javascript:App.js
import './App.css';

function App() {
  return (
    <div className="App">Hello</div>
  );
}

export default App;
```
#### Node.jsの設定

右下の＋マークを押下し、新しいターミナルを開く
![スクリーンショット 2022-04-10 1.43.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/524363ef-aad0-563b-2f90-52385127ba68.png)
serverディレクトリへ移動
```
$ cd ..
$ cd server/
```
node.jsアプリケーションを作成する
```
$ npm init
```
![スクリーンショット 2022-04-10 1.48.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/c429bf28-7083-86a6-77a1-1cc3563ff39f.png)
表示される質問に対しては、すべて「Enter」でOK
serverディレクトリの中にpackage.jsonが作成される
![スクリーンショット 2022-04-10 1.50.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/8885f204-25fc-b3c0-03d1-cf71276302d3.png)

serverディレクトリの中にindex.jsファイルを作成
![スクリーンショット 2022-04-10 1.59.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/17f06e20-1fb6-4f5a-0386-5fa15a523847.png)
必要なモジュールのインストール
```
$ npm install express mysql2 nodemon cors

express: フレームワーク
mysql2: DB。無印より2が良い
nodemon: ソースを監視して、自動でサーバーを再起動してくれるツール
cors: cors対策
```
node.js側の画面表示
```javascript:index.js
const express = require('express');
const app = express();

app.get("/", (req, res) => {
    res.send("hello world");
});

app.listen(3001, () => {
    console.log('running on port 3001');
});

```
node.jsを実行
```
$ node index.js
```
![スクリーンショット 2022-04-10 2.16.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/e3818b6d-3059-82f7-9881-bd1ad8290bbf.png)
3001番ポートで起動
![スクリーンショット 2022-04-10 2.16.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/ce4409b0-4f39-01cb-eec4-5d0db392f279.png)

node.jsのサーバーがファイル編集をトリガーに自動で再起動するように設定

```diff_json:package.json
  "scripts": {
+    "start": "node index.js",
+    "devStart": "nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
```

右側のターミナルで実行中のものは一度Ctrl+cで終了し、再度以下コマンドで起動
```
$ npm run devStart
```
MySQLに接続し、任意のテーブルからデータを取得して返す
```diff_javascript:index.js
const express = require('express');
const app = express();
+ const mysql = require('mysql2');

+ const db = mysql.createPool({
+   host: 'localhost',
+   user: 'root',
+   password: 'password',
+   database: 'dbname',
+ });

app.get("/", (req, res) => {

+   const sqlSelect = "SELECT * FROM category ORDER BY id";
+   db.query(sqlSelect, (err, result) => {
+       res.send(result);
+   });

-   res.send("hello world");
});

app.listen(3001, () => {
    console.log('running on port 3001');
});
```
画面にSELECT結果が表示されれば成功
![スクリーンショット 2022-04-10 12.57.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/9d08e653-7a06-258c-b617-c17a302f026b.png)
続いて、client側から呼び出すため、リクエストURLをAPI形式に変更
また併せてCORS対策を追加
```diff_javascript:index.js
const express = require('express');
+ const cors = require('cors');
const app = express();
const mysql = require('mysql2');

:

+ app.use(cors());

+ app.get("/api/get/category", (req, res) => {
    const sqlSelect = "SELECT * FROM category ORDER BY id";
    db.query(sqlSelect, (err, result) => {
        res.send(result);
    });
});

:

```
client側では、先ほど作成したAPIを叩いて結果をリスト表示します。

補足：Reactのライフサイクルは以下の3つ
Mounting（生まれた時）　→ Updating（状態が変化した時）　→ Unmounting（コンポーネントがなくなる時）
useEffectの第二引数に空の配列を渡すと、Mounting時のみ呼ばれるようになるのでページ表示時のみAPI実行となる。
第二引数の値が変化した時だけ呼ばれるようになるので、仮にstateを入れていると、Mounting時、stateの変化時(Updating)に呼ばれるようになる。

```diff_javascript:index.js
import React, { useState, useEffect } from "react";
import './App.css';
+ import Axios from "axios";

function App() {
+ const [categoryList, setCategoryList] = useState([]);

+ useEffect(() => {
+   Axios.get("http://localhost:3001/api/get/category").then((response) => {
+     setCategoryList(response.data);
+   });
+ }, []);
  return (
    <div className="App">
+     <ul>
+       {categoryList.map((val, index) => {
+         return <li key={index}>{val.name}</li>
+       })}
+     </ul>
    </div>
  );
}

export default App;
```
以下のように表示されたら成功です。
![スクリーンショット 2022-04-10 16.10.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547737/a5e5d36e-c8f2-efd3-765d-6f5c14110b79.png)
#### おまけ
このようにNode.jsのindex.jsに対して複数APIを記述していくことが可能。
postパラメータを受け取ってからのinsertの例も追加しておく
```diff_javascript:index.js
:
app.get("/api/get/category", (req, res) => {
    const sqlSelect = "SELECT * FROM category ORDER BY id";
    db.query(sqlSelect, (err, result) => {
        res.send(result);
    });
});

+ app.post("/api/insert/category", (req, res) => {
+   const name = req.body.name;
+   const sqlInsert = "INSERT INTO category (name) VALUES (?)";
+   db.query(sqlInsert, [name], (err, result) => {
+       console.log(result);
+   });
+ });
:
```
```diff_javascript:App.js
:
  useEffect(() => {
    Axios.get("http://localhost:3001/api/get/category").then((response) => {
      setCategoryList(response.data);
    });
  }, []);

+ const submitCategory = () => {
+   Axios.post("http://localhost:3001/api/insert/category", {
+     name: categoryName,
+   }).then(() => {
+     alert("successful insert");
+   });
+ };
:
```