Node.js Express + React の Proxy 設定について

# この記事について
Node.js Express をキャッチアップし、アウトプットしようと簡単なWebアプリを作成したとき、Proxyの設定に少々つまづいたので備忘録。

# やりたいこと
- Github APIを利用し、Githubのクローンを作成したい（ユーザー情報閲覧、フォロー、フォロワー、リポジトリ、ユーザー検索機能のみ）。
- ServerサイドでAPIを作成（Node.js Express）。
- ClientサイドはReactで作成。

# バージョン
```
$ node -v
v14.17.0

$ npm -v
6.14.13

express: 4.18.1
react: 18.1.0
```

# 想定
- React project ・・・`http://localhost:3000`
- Express project ・・・`http://localhost:5000`

ユーザーは`http://localhost:3000`にアクセスすることでReactによって描画されたブラウザを閲覧できる。
アプリケーション内でGithubユーザーの検索や、フォロワー情報の取得などする際は、Express側で取得したGithub APIの情報をclient側へ渡す。
client（React）側から直接外部APIの呼び出しは行わない（通常client側からDBアクセスはしないことを想定）。
`app/client/package.json`に`"proxy"`Keyを設定し`"http://localhost:5000"`をValueとすることでExpressで作成したAPIサーバーへアクセスできる。
```json:app/client/package.json
{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.2.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "proxy": "http://localhost:5000"  // この行を追加
}
```

# 処理の流れ
フォロワー一覧取得の場合
1. client側から`http://localhost:5000/api/v1/followers`へGETリクエスト。
2. server側でGithub API `https://api.github.com/users/{userName}/followers`へGETリクエスト。
3. 受けっとったレスポンスを必要な情報のみに整形。
3. 結果をclientへ返す。
3. Reactでスタイリングし、ブラウザへ描画。 

# ディレクトリ構成
```
app
 ├── client
 │      ├── node_modules
 │      ├── src
 │      │   　　├── app
 │      │   　　│    └── App.jsx
 │      │  　　 ├── index.css
 │      │  　　 └── index.js
 │      └── package.json
 │
 ├── node_modules
 ├── server
 │   　　　　 └── server.js // Express で API 作成
 └── package.json
```

# 実行
`/app/package.json`のコマンドを実行
```json:app/package.json
"scripts": {
    "client-install": "cd client && npm install",
    "client": "npm start --prefix client",
    "server": "nodemon server/server.js",
    "dev": "concurrently \"npm run server\" \"npm run client\""
}
```
```
$ pwd
/app

$ npm run dev
```

# 実行エラー
コンソールエラー
```
[1] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
[1]  - options.allowedHosts[0] should be a non-empty string.
[1] npm ERR! code ELIFECYCLE
[1] npm ERR! errno 1
[1] npm ERR! client@0.1.0 start: `react-scripts start`
npm ERR! Exit status 1
[1] npm ERR! 
[1] npm ERR! Failed at the client@0.1.0 start script.
[1] npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
[1] 
[1] npm ERR! A complete log of this run can be found in:
[1] npm ERR!     /Users/.npm/_logs/2022-05-21T03_09_52_961Z-debug.log
[1] npm ERR! code ELIFECYCLE
[1] npm ERR! errno 1
[1] npm ERR! react-express-api@1.0.0 client: `npm start --prefix client`
[1] npm ERR! Exit status 1
npm ERR! 
[1] npm ERR! Failed at the react-express-api@1.0.0 client script.
[1] npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
[1] 
[1] npm ERR! A complete log of this run can be found in:
[1] npm ERR!     /Users/.npm/_logs/2022-05-21T03_14_06_437Z-debug.log
[1] npm run client exited with code 1
```
指定されたログファイル
```log:/Users/.npm/_logs/2022-05-21T03_14_06_437Z-debug.log
〜〜　省略　〜〜
14 verbose pkgid react-express-api@1.0.0
15 verbose cwd /Users/project/app
16 verbose Darwin 21.4.0
17 verbose argv "/Users/.nodebrew/node/v14.17.0/bin/node" "/Users/.nodebrew/current/bin/npm" "run" "dev"
18 verbose node v14.17.0
19 verbose npm  v6.14.13
20 error code ELIFECYCLE
21 error errno 1
22 error react-express-api@1.0.0 dev: `concurrently "npm run server" "npm run client"`
22 error Exit status 1
23 error Failed at the react-express-api@1.0.0 dev script.
23 error This is probably not a problem with npm. There is likely additional logging output above.
24 verbose exit [ 1, true ]
```
client側のみ立ち上げてみる
```
$ pwd
/app/client

$ npm run start
```
コンソールエラー
```
Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
 - options.allowedHosts[0] should be a non-empty string.

When specified, "proxy" in package.json must be a string.
[1] Instead, the type of "proxy" was "object".
[1] Either remove "proxy" from package.json, or make it a string.
```
昨日までこの設定で動かして開発していたのに、突然動かなくなり頭の中が ???? でした。
# 解決法
`app/client/package.json`に設定した`"proxy"`を削除
```json:app/client/package.json
〜〜　省略　〜〜
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "proxy": "http://localhost:5000"  // この行を削除
}
```

新たに`http-proxy-middleware`というパッケージを追加し、`app/client/src/setupProxy.js`というファイルを作成します。
```
$ pwd
/app/client

$ npm install http-proxy-middleware
```
```js:app/client/src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = (app) => {
	app.use(
		'/api/*',
		createProxyMiddleware({
			target: 'http://localhost:5000',
			changeOrigin: true,
		})
	);
};
```
`create-react-app`が、自動的に`src/setupProxy.js`このファイルを探すので、importなどは不要とのこと。

# 修正後ディレクトリ構成
```
app
 ├── client
 │      ├── node_modules
 │      ├── src
 │      │   　　├── app
 │      │   　　│    └── App.jsx
 │      │  　　 ├── index.css
 │      │  　　 ├── index.js
 │      │    └──　setupProxy.js  // 新たに追加
 │      └── package.json
 │
 ├── node_modules
 ├── server
 │   　　　　 └── server.js
 └── package.json
```


# 動作確認
まずはclient側だけ立ち上げてみる
```
$ pwd
/app/client

$ npm run start
```
無事成功！

client側、 server側同時に立ち上げてみる

```
$ pwd
/app

$ npm run dev
```
無事成功！！

# まとめ
途中にも書きましたが、前日まで動いていたのに、突然動かなくなった理由はわかりません。
しかし、この修正でより安定した開発ができそうです。
原因がわかる方いらっしゃいましたら、ご教授ください。
また、今Webアプリの全貌は別記事にまとめる予定ですので、よろしければそちらもご覧ください。

# 参考にしたサイト
https://github.com/facebook/create-react-app/issues/11762#issuecomment-1001295074

https://www.ey-office.com/blog_archive/2022/03/03/learned-the-proxy-setting-of-create-react-app/

https://necrates.hatenablog.jp/entry/2020/08/14/095644