フロントをReactで作成したSpringBootWebアプリケーションのjarを作成してWebサーバーを起動する

## はじめに

バックエンドとフロントエンドを順番に作成し、動作確認を行い、一つにまとめた状態のTomcat内蔵のjarファイルを最後に作成します。

## Springプロジェクトの作成
Eclipseで作成します。
名前を`backend`に変えるのみでその他はデフォルトのまま、Springスタータープロジェクトで作成。
作成場所はWindowsのファイルシステム内の`C:\Users\Public\<project_root>\backend`にします。

簡易的な足し算を返すコントローラーのみ作成します。

```Java:SumController.java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.Data;

@RestController
@RequestMapping("/api")
public class SumController {

	@PostMapping("/sum")
	public SumResponse sum(@RequestBody SumRequest request) {
		int result = request.getNum1() + request.getNum2();
		return new SumResponse(result);
	}
	
	@Data
	static class SumRequest {
		private int num1;
		private int num2;
	}
	
	@Data
	static class SumResponse {
		private final int sum;
	}

}

```

Springプロジェクトとして起動します。
これはWindowsシステムで動いているので、WSLから疎通確認を行う際はIPアドレスの指定をする必要があるので、PowerShellでIPアドレスを確認。

```terminal
ipconfig
    // IPv4 アドレス . . . . . . . . . . . .: 192.168.11.59
```

WSLからcurlで疎通確認。
```terminal
curl -X POST http://192.168.11.59:8080/api/sum      -H "Content-Type: application/json"      -d '{"num1": 5, "num2": 3}'
    // {"sum":8}
```

問題なし。

## Reactプロジェクトの作成
Nodeの環境が必要なので、WSLからWindowsのファイルシステムにアクセスし`C:\Users\Public\<project_root>\frontend`を作成しました。

（余談：チームに展開するときも、Windowsシステム上にCloneしてもらえば良さそう）

```tsx:Sum.tsx
import { FC, useState } from "react";
import axios from "axios";

const Sum:FC = () => {

  // Spring Boot のエンドポイント
  const API_URL = "http://192.168.11.59:8080/api/sum";

  const [num1, setNum1] = useState<number>(0);
  const [num2, setNum2] = useState<number>(0);
  const [sum, setSum] = useState<number | null>(null);
  const [error, setError] = useState<string | null>(null);

  const handleSum = async () => {
    setError(null);
    try {
      const response = await axios.post(API_URL, { num1, num2 });
      setSum(response.data.sum);
    } catch (err) {
      setError("計算失敗");
      console.error(err);
    }
  };

  return (
    <div>
      <h2>足し算</h2>
      <div>
        <label>数値1:</label>
        <input type="number" value={num1} onChange={(e) => setNum1(Number(e.target.value))} />
      </div>
      <div>
        <label>数値2:</label>
        <input type="number" value={num2} onChange={(e) => setNum2(Number(e.target.value))} />
      </div>
      <button onClick={handleSum}>計算</button>
      {sum !== null && <p>合計: {sum}</p>}
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
};

export default Sum;
```

フロント起動して、API実行！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779836/643f6ac6-e225-4b22-aae2-d584763dc487.png)

何度もこんにちは。CORSエラー。

Spring側に設定を加えます。
```@CrossOrigin(origins = "http://localhost:5173")```を追加。

```
package com.example.demo.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.Data;

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:5173")
public class SumController {

	@PostMapping("/sum")
	public SumResponse sum(@RequestBody SumRequest request) {
		int result = request.getNum1() + request.getNum2();
		return new SumResponse(result);
	}
	
	@Data
	static class SumRequest {
		private int num1;
		private int num2;
	}
	
	@Data
	static class SumResponse {
		private final int sum;
	}

}

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779836/fc074488-3e61-437c-85ce-a94e5b1187b7.png)

オッケーです。

## jarファイルを作成
お次にReactプロジェクトをビルドし、Springの`static`に配置し、ビルドしてjarモジュールを作成していきます。
Reactで作成した際に、APIの向き先にIPアドレスを指定していましたが、今回はひとつのモジュールにまとめるため、`相対パス`に変更しておきます。

```
const API_URL = "/api/sum";
```

またSpringにて設定していたCORSエラー解消のために入れていた修正も外します。

Reactのビルド
```terminal
npm run build
```

作成されたモジュール群をSpringに組み込みます。

```terminal
cp -r frontend/dist/* ../backend/src/main/resources/static/
```

毎回Reactビルドした後にコピーするのは面倒なので、`npmスクリプト`の`postbuild`を追加します。

```json:package.json
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "postbuild": "cp -r dist/* ../backend/src/main/resources/static/",
    "lint": "eslint .",
    "preview": "vite preview"
  },
```


Spring君を起動してアクセスしても、APIリクエストと勘違いしちゃうおっちょこちょいさんなので、Reactのルーティングを認識してもらいます。

```java:src/main/java/com/example/demo/config/WebConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/{spring:[a-zA-Z0-9-_]+}")
                .setViewName("forward:/index.html");
    }
}

```

Module化する前にEclipseで起動して確認。

`http://localhost:8080`にアクセス。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779836/d6aaf6b1-0e4c-4def-8e32-2a998945eebb.png)

ここまでも問題なく進めました。

### jarにする

EclipseのGradleタスクから、`bootjar`を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779836/4115f3ab-22c8-485a-aff0-c1627a4c3a43.png)

## 動作確認
最後に作成されたjarファイルを実行し、動作確認を行います。

```terminal
java -jar <root_path>/build/libs/backend-0.0.1-SNAPSHOT.jar
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779836/efa37ed9-81b6-4759-ae9e-5c23a205537b.png)

`localhost:8080`にアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779836/b6a1ae80-553b-4e41-997d-106355d9820c.png)

## おわりに
プロジェクトの構成は以下のようになっているので、このままGitリポジトリにしてしまえばフロントエンドもバックエンドも一つのリポジトリで管理できます。

```terminal
.
├── Servers
├── backend
└── frontend
``