IBM Cloud Code Engine に React + Express アプリケーションをデプロイしてみる

# はじめに

前回の記事でIBM Cloud Code EngineにPlay Framework/Scalaアプリケーションをデプロイする方法を紹介しましたが、今回は同様のプロセスをNode.jsベースのアプリケーションでも行なってみます。このNode.jsアプリケーションはフロントエンドにReactを用い、バックエンドにはExpressを使ってサーバーを構築します。プロダクションへのデプロイ時にはバックエンドのExpressサーバーが両方ともホストすることになります。（ネット上にこの構成でクラウド環境へのデプロイまでカバーしている記事があまり多くはなかったので記述しておくという意味合いもあります。）

# Expressサーバーの構築

Expressサーバーを構築するのに`express-generator`を使用します。

```shell
$ npm i -g express-generator
```

早速Expressサーバーの雛形を作成します。

```shell
$ express react-express
```

最終的にフロントエンドはReactで書くことになるので、ここではテンプレートエンジンの設定等を除いていきます。まず`public`と`views`ディレクトリは削除します。次に`package.json`から以下の行を削除します。

```json:package.json
    "jade": "~1.11.0",
```

次に`app.js`からも以下の行を削除します。

```javascript:app.js
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

```

`indexRouter`関連の行も削除して構いません。また、後述のReactアプリケーション側とポートが衝突しないように、`bin/www`も変更しておきます。

```javascript:bin/www
var port = normalizePort(process.env.PORT || '8080');
app.set('port', port);
```

これだけで既にweb application serverとして起動できます。

```shell
$ npm i
$ npm start

> react-express@0.0.0 start
> node ./bin/www

```
ブラウザから`localhost:8080/users`にアクセスして確認すると以下のようになります。

![スクリーンショット 2021-12-28 13.51.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/48d3878f-8a7f-5572-1129-7a2071522a7d.png)

# Reactアプリケーションの作成
Reactアプリケーションを[Create React App](https://github.com/facebook/create-react-app)を使って作っていきます。最終的に`client`以下にReactのコードを配置したいので、ディレクトリ名を`client`とするため以下のコマンドで作成します。

```shell
$ npx create-react-app client
```
出来上がったら`client`ディレクトリに移動して`npm start`すると見慣れたトップページが表示されます。
![スクリーンショット 2021-12-28 13.28.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/49f2c636-0346-1a44-e36b-8ed376342612.png)

これでExpressサーバーとReactアプリケーションが起動していますが、ばらばらに動いている状態です。これを接続するため、まずはproxyの設定をします。`client/package.json`に`proxy`を定義します。

```json:client/package/json
  "proxy": "http://localhost:8080",
```

`client/src/App.js`を改変して`/users/`を呼び出して結果を表示するように変更します。

```javascript:client/src/App.js
import logo from './logo.svg';
import './App.css';
import {useState, useEffect} from 'react';

function App() {
  const [users, setUsers] = useState('');
  useEffect(() => {
    fetch('/users/')
      .then((res) => res.text())
      .then((text) => setUsers(text));
  }, []);
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Hello {users}!
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

```

ブラウザ上で表示を確認します。

![スクリーンショット 2021-12-28 14.17.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/fcf68c9f-1195-de0b-b72d-d55575ff4809.png)

# プロダクション用の構成

この構成で開発を進めることはできますが、`client`ディレクトリでの`npm start`はdevelopment modeでの動作な上、このままでは2つのサーバーを起動しなければいけないのでプロダクションでの使用には適しません。

プロダクション用のビルドを行うため、以下のように`package.json`を変更します。

```json:package.json
  "scripts": {
    "build": "cd client && npm run build && rm -rf ../public && mv build ../public",
    "start": "node ./bin/www"
  },
```

この変更を行なって`npm run build`を行うと、まずclientディレクトリ内で`npm run build`を行うことでプロダクション用のReactアプリケーションが`build`ディレクトリ以下に生成され、それを親ディレクトリの`public`ディレクトリとして配置することになります。こうすることで、`app.js`内の以下の行によってExpressサーバーがReactで書かれたフロントエンドアプリケーションを提供するようになります。

```javascript:app.js
app.use(express.static(path.join(__dirname, 'public')));
```

Expressサーバーを再起動すると、ブラウザの8080番でアプリケーションが動作していることが確認できます。

![スクリーンショット 2021-12-28 14.36.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/9ab50103-782e-2a1b-fc04-538eae50a4aa.png)


# IBM Cloud Code Engineへのデプロイ

アプリケーションは完成したので、あとはこれをCode Engineへデプロイできるように`Dockerfile`にまとめていきます。

```dockerfile:Dockerfile
FROM node:16 as builder

RUN mkdir /home/node/app
WORKDIR /home/node/app
COPY . .

# Run install for React app
RUN cd client && npm install
# Run install for the application (w/ Express server)
RUN npm install
# Produce production React app
RUN npm run build
# Once application is generated, client code is not needed any more
RUN rm -rf client

FROM node:16
COPY --from=builder /home/node/app /home/node/app
RUN chown -R node:node /home/node/app

USER node
WORKDIR /home/node/app

EXPOSE 8080
CMD ["node", "bin/www"]
```

ローカルで実験する時のために `.dockerignore`も適宜指定しておくと良いと思います。

```text:.dockerignore
Dockerfile
.dockerignore
node_modules
public
client/build
client/node_modules
```

ローカルでビルドして立ち上げてみます。

```shell
$ docker build . -t react-express
$ docker run -p 8080:8080 --rm react-express:latest
```

ブラウザからアクセスして先ほどと同じページが表示されるのを確認できたら、ソースコードをGitHubにpushしておきます。

GitHub経由でコードが取得できるようになったら、IBM CloudコンソールからCode Engineのページを開き、前回と同様にGitHubリポジトリを指定してデプロイします。手順は前回とほぼ変わりません。今回は既に8080ポートでアプリケーションが起動するのでlistenポートも空欄で大丈夫です。ビルド詳細でイメージ名等を設定したら作成をクリックして完了です。

![スクリーンショット 2021-12-28 16.23.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/1d1ff95d-5157-7b53-8539-10f0fdc9b9da.png)

アプリケーションが作成されたら、ビルドが終わってデプロイされるのを待ちます。デプロイが完了したらブラウザからアプリケーションが動作していることが確認できます。

![スクリーンショット 2021-12-28 16.10.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/5fc733fc-1a8d-a5da-7f77-1336f7133054.png)

上のdeveloper toolsを見るとわかるように、フロントエンドからバックエンドの`/users`を呼び出していて、フロントエンドのアプリケーションもバックエンドのAPIも正しくExpressサーバーで提供されていることがわかります。

# まとめ
Expressサーバーの構成に`express-generator`、Reactアプリケーションの作成に`create-react-app`を使い、開発環境の作成及びCode Engineへのデプロイまでの手順をまとめました。

# 参考
上記で使ったソースコードはこちらにあります（後の記事での変更も含んでいます）： https://github.com/fterui/react-express