Gradle multi-project 内に React アプリ(frontend-app)と Spring Boot アプリ(backend-app)を作成する
概要
記事一覧はこちらです。
Gradle multi-project 内に React+Typescript+Tailwind CSS+React Query 構成の frontend 用アプリと、Sping Boot+springdoc-openapi 構成の REST API を提供する backend 用アプリを作成し、以下の内容を実現します。
- Spring Boot の REST API には @CrossOrigin を付与しません。
- 開発中も本番も frontend から backend の REST API を呼び出す時の URL はパス名のみの
/user/1
にします(http://localhost:8080/user/1
のようにホスト名等は記述しません)。 - 開発中は CRACO で
yarn start
で起動する webpack-dev-server に proxy を設定して、webpack-dev-server 経由で backend 用アプリの REST API にアクセスします。 ※Configuring the Proxy Manually に記述されている src/setupProxy.js を作成・設定する方法もありますが、Tailwind CSS を使う時に CRACO を入れているので今回は CRACO を使う方法を採用しています。 - build タスク実行時に
yarn build
コマンドを実行 → React アプリのファイルを Spring Boot アプリへコピーしてから、Spring Boot アプリの jar ファイルを生成します。
今回作成したソースは ksbysample-react-springboot に置いています。
参照したサイト・書籍
React Query
https://react-query.tanstack.com/CRACO - Configuration
https://github.com/gsoft-inc/craco/blob/master/packages/craco/README.mdwebpack - DevServer - devServer.proxy
https://webpack.js.org/configuration/dev-server/#devserverproxyCreate React App - Proxying API Requests in Development
https://create-react-app.dev/docs/proxying-api-requests-in-development/node-gradle / gradle-node-plugin - Usage
https://github.com/node-gradle/gradle-node-plugin/blob/master/docs/usage.md
目次
- ksbysample-react-springboot レポジトリを作成して clone する
- Spring Initializr でサンプルプロジェクトを作成して Gradle Wrapper のファイルをコピーする
gradlew init
を実行し、build.gradle があるサブプロジェクトを自動認識するよう settings.gradle を変更する- backend-app サブプロジェクトを作成し、REST API を実装する
npx create-react-app frontend-app --template typescript
を実行して frontend-app サブプロジェクトを作成する- Tailwind CSS、CRACO をインストールする
- React Query をインストールする
- frontend-app に REST API を呼び出して取得したデータを表示する処理を実装する
- craco.config.js を変更し webpack-dev-server に proxy の設定を追加する
- Spring Boot アプリを IDEA から起動、React アプリを
yarn start
で起動して動作確認する - frontend-app サブプロジェクトで Gradle の build タスクが実行されるようにし、かつ build タスク実行時に
yarn build
コマンドが実行されるように設定する - backend-app サブプロジェクトで build タスク実行時に frontend-app の build 下のファイルを backend-app/src/main/resources/static にコピーされるように設定する
- プロジェクトのルートディレクトリで Gradle の build タスクを実行する
- 生成された jar ファイルで Spring Boot アプリを起動して動作確認する
手順
ksbysample-react-springboot レポジトリを作成して clone する
ksbysample-react-springboot レポジトリ を作成し、D:\project-react\ksbysample-react-springboot に clone します。
Spring Initializr でサンプルプロジェクトを作成して Gradle Wrapper のファイルをコピーする
適当なディレクトリに Spring Initializr で Spring Boot のプロジェクトを作成し(Gradle を指定する)、Gradle Wrapper の以下のファイルを D:\project-react\ksbysample-react-springboot の下にコピーします。
- gradle ディレクトリ
- gradlew
- gradlew.bat
gradlew init
を実行し、build.gradle があるサブプロジェクトを自動認識するよう settings.gradle を変更する
gradlew init
を実行します。
build.gradle があるサブプロジェクトを自動的に認識してくれるよう settings.gradle を以下の内容に変更します。
rootProject.name = 'ksbysample-react-springboot' rootDir.eachFileRecurse { f -> if (f.name == "build.gradle") { String relativePath = f.parentFile.absolutePath - rootDir.absolutePath String projectName = relativePath.replaceAll("[\\\\\\/]", ":") if (projectName != ":buildSrc") { include projectName } } }
IntelliJ IDEA で D:\project-react\ksbysample-react-springboot を開きます。
backend-app サブプロジェクトを作成し、REST API を実装する
Spring Initializr で D:\project-react\ksbysample-react-springboot の下に backend-app サブプロジェクトを作成します。
D:\project-react\ksbysample-react-springboot\backend-app の下は src ディレクトリ、build.gradle 以外のディレクトリ、ファイルを削除します。
build.gradle を以下の内容に変更します。
plugins { id 'org.springframework.boot' version '2.4.5' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'ksbysample' version = '0.0.1-SNAPSHOT' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 [compileJava, compileTestJava]*.options*.encoding = "UTF-8" [compileJava, compileTestJava]*.options*.compilerArgs = ["-Xlint:all,-options,-processing,-path"] configurations { compileOnly.extendsFrom annotationProcessor // annotationProcessor と testAnnotationProcessor、compileOnly と testCompileOnly を併記不要にする testAnnotationProcessor.extendsFrom annotationProcessor testImplementation.extendsFrom compileOnly } repositories { mavenCentral() } dependencies { def lombokVersion = "1.18.20" implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' // for lombok // testAnnotationProcessor、testCompileOnly を併記しなくてよいよう configurations で設定している annotationProcessor("org.projectlombok:lombok:${lombokVersion}") compileOnly("org.projectlombok:lombok:${lombokVersion}") } test { useJUnitPlatform() }
backend-app/src/main/java/ksbysample/backendapp/User.java を作成し、以下の内容を記述します。
package ksbysample.backendapp; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor @Builder public class User { private String name; private int age; }
backend-app/src/main/java/ksbysample/backendapp/UserApiController.java を作成し、以下の内容を記述します。
package ksbysample.backendapp; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserApiController { @GetMapping("/{id}") public User findById(@PathVariable String id) { return User.builder() .name(String.format("Tanaka Taro No.%s", id)) .age(25) .build(); } }
npx create-react-app frontend-app --template typescript
を実行して frontend-app サブプロジェクトを作成する
D:\project-react\ksbysample-react-springboot で npx create-react-app frontend-app --template typescript
コマンドを実行し、frontend-app サブプロジェクトを作成します。
cd /d/project-react/ksbysample-react-springboot/frontend-app
した後 yarn start
コマンドを実行し、画面が表示されることを確認します。
Tailwind CSS、CRACO をインストールする
以下のコマンドを実行し Tailwind CSS、CRACO をインストールします(React アプリと Spring Boot アプリを連携するのに Tailwind CSS は必須ではありません)。
yarn add tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat
yarn add -D postcss@^7 autoprefixer@^9
yarn add @tailwindcss/forms
yarn add -D @craco/craco
package.json 内の scripts で react-scripts
→ craco
に変更します。
"scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "craco eject" },
プロジェクトのルートディレクトリ直下に craco.config.js を作成し、以下の内容を記述します。
module.exports = { style: { postcss: { plugins: [ require('tailwindcss'), require('autoprefixer'), ], }, }, }
npx tailwindcss init -p
を実行して tailwind.config.js、postcss.config.js を作成した後、tailwind.config.js を以下の内容に変更します。
module.exports = { purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], darkMode: false, // or 'media' or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [ require('@tailwindcss/forms'), ], }
frontend-app/src/index.css を以下の内容に変更します。
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind forms;
ここに書いた内容については React+Tailwind CSS+Storybook のプロジェクトを作成する 参照。
React Query をインストールする
React アプリから Spring Boot アプリの REST API を呼び出すのに React Query を使用します。yarn add react-query
コマンドを実行してインストールします。
frontend-app に REST API を呼び出して取得したデータを表示する処理を実装する
frontend-app/src/index.tsx を以下のように変更します。
import React from 'react'; import ReactDOM from 'react-dom'; import { QueryClient, QueryClientProvider } from 'react-query'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; const queryClient = new QueryClient(); ReactDOM.render( <React.StrictMode> <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> </React.StrictMode>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
- 以下の行を追加します。
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();
<App />
の前後に<QueryClientProvider client={queryClient}> ... </QueryClientProvider>
を追加します。
frontend-app/src/App.tsx を以下の内容に変更します。REST API を呼び出す時の URL は /user/1
固定です。
import { FC } from 'react'; import { useQuery } from 'react-query'; type User = { name: string; age: number; }; const getUser = async () => (await fetch('/user/1')).json(); const App: FC = () => { const { data: user } = useQuery<User>(['user', 1], () => getUser()); return ( <div className="pt-4 pl-4 text-red-600 text-4xl font-extrabold"> {user?.name} は {user?.age} 歳です。 </div> ); }; export default App;
craco.config.js を変更し webpack-dev-server に proxy の設定を追加する
frontend-app/craco.config.js に devServer: { ... }
を追加して、/user/...
でアクセスされたら Spring Boot アプリに転送するようにします。
module.exports = { style: { postcss: { plugins: [require('tailwindcss'), require('autoprefixer')], }, }, devServer: { proxy: [ { context: ['/user'], target: 'http://localhost:8080', changeOrigin: true, }, ], }, };
Spring Boot アプリを IDEA から起動、React アプリを yarn start
で起動して動作確認する
Spring Boot アプリを IDEA から起動します。
React アプリを yarn start
コマンドで起動すると、
ブラウザ上に REST API から取得したデータが表示されました。
Spring Boot アプリ、React アプリを停止します。
frontend-app サブプロジェクトで Gradle の build タスクが実行されるようにし、かつ build タスク実行時に yarn build
コマンドが実行されるように設定する
サブプロジェクトに build.gradle を作成すれば Gradle multi-project のサブプロジェクトとして認識されるので、frontend-app の直下に build.gradle を作成し、以下の内容を記述します。
plugins { id "base" id "com.github.node-gradle.node" version "3.0.1" } task yarnBuild(type: YarnTask) { args = ["build"] } build.dependsOn yarnBuild
- base プラグインを追加し、frontend-app サブプロジェクトで build、clean タスクが実行できるようにします。
- gradle-node-plugin プラグインを追加し、build タスク実行時に
yarn build
コマンドが実行されるようにします。
IDEA の Gradle Tool Window で Reload ボタンをクリックすると frontend-app が Gradle のサブプロジェクトとして認識・表示され、その下に build、clean タスクが表示されます。
backend-app サブプロジェクトで build タスク実行時に frontend-app の build 下のファイルを backend-app/src/main/resources/static にコピーされるように設定する
backend-app/build.gradle の一番下に以下の記述を追加します。
.......... clean.delete fileTree("src/main/resources/static").include("**/*") task copyToStatic(type: Copy) { from project(":frontend-app").file("build") into "src/main/resources/static" } copyToStatic.dependsOn ":frontend-app:build" processResources.dependsOn copyToStatic
- clean タスク実行時に src/main/resources/static の下をクリアするように設定します。
- build タスク実行時に、processResources タスクの前で copyToStatic タスクを実行し、frontend-app の build ディレクトリの下にあるディレクトリ・ファイル一式を backend-app/src/main/resources/static の下にコピーします。
IDEA の Gradle Tool Window で Reload ボタンをクリックしておきます。
プロジェクトのルートディレクトリで Gradle の build タスクを実行する
IDEA の Gradle Tool Window から clean → build タスクを実行すると、
:frontend-app:yarnBuild タスクの後に :backend-app:copyToStatic タスクが実行されて、
backend-app/src/main/resources/static の下に React アプリのファイルがコピーされています。
生成された jar ファイルで Spring Boot アプリを起動して動作確認する
コマンドプロンプトで backend-app/build/libs の下に移動してから java -jar backend-app-0.0.1-SNAPSHOT.jar
コマンドを実行して Spring Boot アプリを起動した後、
ブラウザから http://localhost:8080/ にアクセスすると REST API から取得したデータが表示されました。
履歴
2021/04/29
初版発行。