プログラミング

SpringBoot×Reactを1つのEC2インスタンスにデプロイ【NginxでCORS回避】

この記事は約8分で読めます。

こんにちは。じゅんです。

前回ローカル環境にSpring BootとReactの簡単メモアプリを作成しました。
今回はそのメモアプリを1つのEC2インスタンスにデプロイしていこうと思います。

WebサーバとAPサーバを分けてデプロイした際の記事はこちらです。

これまでの作業内容については以下の記事にまとめています。

事前準備

AWSアカウントの開設・セットアップ

ローカル環境でのアプリの開発

全体像

Nginxをリバースプロキシとして使います。

インスタンスの作成

ReactとSpring Bootを起動するインスタンスを作成します。

項目名意味
名前とタグ > 名前simple-memo
-web-server(任意)
インスタンスの名前
キーペア (ログイン) > キーペア名 > 新しいキーペアを作成simple-memoインスタンスに接続するときの認証情報
APサーバーと同じものを使用
ネットワーク設定 > 右上編集ボタン > ファイアウォール(セキュリティグループ) > セキュリティグループを作成するsimple-memo-web-sg
ネットワーク設定 > 右上編集ボタン > インバウンドセキュリティグループのルール > セキュリティグループルール 1タイプ:ssh
ソース:3.112.23.0/29
インスタンスを操作するための接続(ssh)をEC2 Instance Connectのみに制限
ネットワーク設定 > 右上編集ボタン > インバウンドセキュリティグループのルール > セキュリティグループルール 2タイプ:ssh
ソースタイプ:マイIP
jarファイル転送時にscpコマンドでssh接続するため使用しているPCのみ許可
ネットワーク設定 > 右上編集ボタン > インバウンドセキュリティグループのルール > セキュリティグループルール 3タイプ:HTTP
ソースタイプ:0.0.0.0/0
Nginxへのアクセスを解放
他の項目はデフォルトのままです。

Nginxの設定

今回、Nginxをリバースプロキシとして使います。

赤枠の部分です

外部からのアクセスをNginxがポート80で受け取り、Reactが起動しているポート3000に代わりにリクエストを送ります。

ReactとSpringBoot間のAPI通信もNginxを介して行います。
Reactからのhttp://(パブリックIP)/api/へのリクエストをNginxが受け取り、http://localhost:8080/apiに代わりにリクエストを送ります。

nginxのインストールと設定についてはこちらの記事を参考にしてください。

/etc/nginx/nginx.conf
server {
    listen       80;
    server_name  localhost;

    # 外部 → Nginx → React
    location / {
        proxy_pass http://localhost:3000;
    }

    # React → Nginx → Spring Boot
    location /api {
        proxy_pass http://localhost:8080/api;
    }
}
React(ポート3000)とSpringBoot(ポート8080)が直接通信を行わない(Nginxが仲介する)ためCORS問題を回避することができます。

nginxを起動します。

Zsh
sudo systemctl start nginx

ソースコードの修正

Nginxで通信の仲介を行うためReactとSpringBootのソースコードに修正が必要です。

React

開発環境ではSpringBootに直接リクエストを送っていましたが、本番環境ではNginxがhttp://(パブリックIP)/api/で待ち受けているためURLを修正します。

src/App.tsx
// const response = await fetch(`http://localhost:8080/api/memos`);
const response = await fetch(`http://xxx.xxx.xxx.xxx/api/memos`);

Spring Boot

Nginxが仲介してCORS問題が発生しないため@CrossOriginアノテーションは不要です。

src/main/java/com/example/simplememo/controller/SimpleMemoController.java
// @CrossOrigin(origins = "http://localhost:3000")
public class SimpleMemoController {
// メソッドなど
}

Spring Bootのデプロイ

Spring Bootのみデプロイする方法はこちらで詳しくまとめています。

開発環境からjarファイルを転送したら、以下のコマンドでjarファイルを実行します。

Zsh
java -jar ~/simple-memo-0.0.1-SNAPSHOT.jar

実行できたらcurlコマンドで動作確認します。

なお、EC2 Instance Connectではコンソールが1画面しかないので私はタブを複製して実行しました。

コマンドの詳細については以下の記事を参考にしてください。

Zsh
# メモを作成
$ curl -s -X POST -H "Content-Type: application/json" -d 'サンプル0' http://(パブリック
IP)/api/memos
0

# メモ一覧を表示
$ curl -s -X GET http://(パブリックIP)/api/memos | jq
[
  {
    "id": 0,
    "content": "サンプル0",
    "createdAt": "2024/05/27 11:19:47",
    "updatedAt": "2024/05/27 11:19:47"
  }
]

Reactのデプロイ

Reactのみデプロイする方法はこちらで詳しくまとめています。

GitからReactアプリをクローンしたら、ビルドして起動してみます。

Zsh
exit                       # ec2-userに戻ります
cd simple-memo-frontend/   # 作業ディレクトリに移動
npm install                # package.jsonに記載されたパッケージをインストール
npm run build
sudo npm install -g serve  # 軽量サーバーを提供するパッケージ
serve -s build             # ビルドされた静的ファイルを軽量サーバーで実行

インスタンスのパブリックIPに接続します。

起動できました。

API通信も確認できました!

【最も苦しんだ箇所】

ReactからのAPI接続先をlocalhostにしていたところAPI通信ができなくて悪戦苦闘しましたが、結局インスタンスのパブリックIPを設定することで解決しました。

その理由を調べたのですが調べ方が悪いのか上手くヒットしませんでした。

個人的な仮説としては、Reactが送信するlocalhostは
Reactが起動しているサーバーのパブリックIPアドレス
ではなく
ブラウザが起動している端末のIPアドレス
として展開されるのではないか、ということです。

まとめ

以上がSpring BootとReactで作成したアプリケーションを1つのEC2インスタンスにデプロイする方法でした。

Nginxで通信を仲介することでCORS問題を回避する点やクライアント側のlocalhostの中身についての考察など、得られるものが多い回でした。

次回はSpring BootとReactを2つのインスタンスに分けてデプロイしてみようかなと思います。

おまけ

今回からiPad Pro 12.9インチを出先の作業のお供にしているのですが、非常に快適です。

少しでも作業効率を上げたい方やiPad Pro12.9インチ(2018年モデル)に興味がある方はぜひこちらの記事も見てみてください。

参考文献

SpringBoot/React/MySQLの簡単なアプリをAWS EC2にデプロイする - Qiita
ゴール開発環境でアプリは作るけど、どうやって本番環境に公開していいのかわからん!を開発環境でアプリは作れるし、泥臭いけどアプリを公開することができる!に変えることができる!よくあるバック…

コメント