初めに
はじめまして。
今回、学習のため、SpringBoot2とReactJSを把握次第、簡単なTodoListを作成した。
目的
フレームワークSpringBoot2とReactJSを用いての開発をすることで、下記事項が理解できているかを確認する
・Spring Boot Flow Architecture
・RestAPI service
・Redux store
・ReactJS Functional Components
スペック
■言語
Java(17)
JavaScript
HTML
CSS
■DBMS
MySQL 8.0.31
■フレームワーク
ReactJS(18.2.0)
SpringBoot2(2.7.4)
■IDE
FrontEnd: Visual Code
BackEnd: IntelliJ
プロジェクトの機能概要
CRUDという4つの基本機能でTaskを管理するTodoList。
結果
作製方法
■準備
- Install NodeJS(最新バーション)
- Install ReactJS
- Install SDK(投稿で使われるのはcorretto-17)
- Install MySql Server
■FrontEnd
- Github Link: https://github.com/TuanAnh-Dao/todolist/tree/master/frontend
- ディレクトリ構成
index.js
import ReactDOM from "react-dom/client";
import App from "./App";
import "font-awesome/css/font-awesome.min.css";
import 'bootstrap/dist/css/bootstrap.min.css';
import store from "./redux/Store";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
index.js ではReduxStoreのイニットをする。
groupSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import * as listAction from "../../utils/ListAction.js";
import axios from "axios";
import Configuration from "../../Configuration";
export const fetchGroups = createAsyncThunk("groups/fetchGroups", async () => {
const response = await axios.get(Configuration.TODOLIST_URL + `/State`);
const formattedData = listAction.formatDataForDisplay(response.data);
return [...formattedData];
});
export const saveDB = createAsyncThunk("groups/saveDB", async (data, thunkAPI) => {
const response = await axios.put(Configuration.TODOLIST_URL + `/State`, data);
const formattedData = await listAction.formatDataForDisplay(response.data);
return [...formattedData];
});
const groupSlice = createSlice({
name: "groups",
initialState: {
current: [],
default: [],
lastTask: {},
isChanged: false,
loading: false,
error: "",
},
reducers: {
updateState(state, action) {
state.current = [...action.payload];
},
addNewTask(state, action) {
state.current[0].taskList.push(action.payload);
},
setModified(state, action){
state.isChanged = !action.payload
},
resetList(state,action){
state.current = [...state.default]
}
},
extraReducers: {
[fetchGroups.pending]: (state, action) => {
state.loading = true;
},
[fetchGroups.rejected]: (state, action) => {
state.loading = false;
state.error = action.error;
},
[fetchGroups.fulfilled]: (state, action) => {
state.loading = false;
state.current = [...action.payload];
state.default = [...action.payload];
},
[saveDB.pending]: (state, action) => {
state.loading = true;
},
[saveDB.rejected]: (state, action) => {
state.loading = false;
state.error = action.error;
},
[saveDB.fulfilled]: (state, action) => {
state.loading = false;
state.current = [...action.payload];
state.default = [...action.payload];
},
},
});
const { reducer, actions } = groupSlice;
export const { updateState, addNewTask, setModified, resetList } = actions;
export default reducer;
groupSlide.js ではactions, reducers, Storeの構造を定義する。
Configuration.js
const Configuration = {
// TODOLIST_URL: "http://todolistspringboot-env.eba-mrqhaqmj.ap-northeast-1.elasticbeanstalk.com/api/v1"
TODOLIST_URL: "http://localhost:8080/api/v1"
}
export default Configuration;
Configuration.js ではRestAPIのURLを設定する。
■BackEnd
- Github link: https://github.com/TuanAnh-Dao/todolist/tree/master/backend
- ディレクトリ構成
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/>
</parent>
<groupId>com.todolist</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api</name>
<description>Todo list api</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
pom.xml はプロジェクトに関する情報を持つ重要なファイルであり、今回のBackendでは五つのdependencyを使う。
1.spring-boot-starter-data-jpa:https://spring.io/projects/spring-data-jpa
2.spring-boot-starter-web:https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
3.mysql-connector-java:https://mvnrepository.com/artifact/mysql/mysql-connector-java
4.spring-boot-starter-security:https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security
5.lombok:https://www.baeldung.com/intro-to-project-lombok
application.yml
spring:
datasource:
password: [YOUR_DB_PASSWORD]
url: jdbc:mysql://localhost:3306/[YOUR_DB_NAME]?serverTimezone=UTC
username: [YOUR_DB_USERNAME]
jpa:
hibernate:
ddl-auto: create-drop
open-in-view: false
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
application.yml は、主に環境ごとに異なるデータを定義したり、各種ライブラリが使用するデータを定義するのためのYAML形式のファイルである。
WebSecurityConfig.java
package com.todolist.api.security.config;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/api/v1/State/**").permitAll()
.anyRequest().authenticated();
}
}
簡単なRESTFul APIを作ろうと思うので、WebSecurityConfigurerAdapterのClassのMethodのOverrideでWeb SecurityをDisableにする。
Entitiesの作成
- State
package com.todolist.api.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.List;
import java.util.UUID;
import static javax.persistence.CascadeType.*;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "State")
public class State {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(columnDefinition = "Binary(16)")
private UUID id;
private String name;
private Integer stateOrder;
private String color;
@OneToMany(cascade= ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "state_id", referencedColumnName = "id")
private List<Task> taskList;
public State(String name, Integer stateOrder, String color) {
this.name = name;
this.stateOrder = stateOrder;
this.color = color;
}
}
- Task
package com.todolist.api.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDate;
import java.util.UUID;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Table(name = "Task")
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(columnDefinition = "BINARY(16)")
private UUID id;
private String name;
private LocalDate deadline;
public Task(String name, LocalDate deadline) {
this.name = name;
this.deadline = deadline;
}
}
Repositoryの作成
package com.todolist.api.repository;
import com.todolist.api.entity.State;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface StateRepository extends JpaRepository<State, UUID> {
}
spring-boot-starter-data-jpaを利用、基本的なMethodを全部定義されたので、再定義する必要がある。
Serviceの作成
package com.todolist.api.service;
import com.todolist.api.entity.State;
import com.todolist.api.repository.StateRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@AllArgsConstructor
public class StateService {
private final StateRepository stateRepository;
public List<State> getStates(){return stateRepository.findAll();}
public List<State> saveAllStates(List<State> stateList ){
return stateRepository.saveAll(stateList);
}
}
Controllerの作成
package com.todolist.api.controller;
import com.todolist.api.entity.State;
import com.todolist.api.service.StateService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(path = "api/v1/State")
@AllArgsConstructor
public class StateController {
private final StateService stateService;
@GetMapping
public ResponseEntity<List<State>> getStates(){
List<State> stateList = stateService.getStates();
if (stateList.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(stateList ,HttpStatus.OK);
}
@PutMapping
public ResponseEntity<List<State>> updateStates(@RequestBody List<State> stateList){
List<State> stateSavedList = stateService.saveAllStates(stateList);
if (stateSavedList.isEmpty()) {
return new ResponseEntity<>(HttpStatus.FAILED_DEPENDENCY);
}
return new ResponseEntity<>(stateSavedList ,HttpStatus.OK);
}
}
下記の二つのAPIを作る。
HTTPメソッド | URL | Body |
---|---|---|
GET | http://localhost:8080/api/v1/State | No |
PUT | http://localhost:8080/api/v1/State | Yes |
Dataの作成
package com.todolist.api.configuration;
import com.todolist.api.entity.*;
import com.todolist.api.repository.*;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class DefaultConfig {
@Bean
CommandLineRunner stateRunner(StateRepository stateRepository){
return args -> {
State state2 = new State(
"Pending",
0,
"#fff"
);
State state1 = new State(
"To do",
1,
"#fff"
);
State state3 = new State(
"In Progress",
2,
"#fff"
);
State state4 = new State(
"Done",
3,
"#fff"
);
List<State> stateList = new ArrayList<>(List.of(state1,state2,state3,state4));
for (State state: stateList) {
List<Task> taskList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Task task = new Task(
String.valueOf(i),
LocalDate.of(2022,Month.OCTOBER,13));
taskList.add(task);
}
state.setTaskList(taskList);
}
stateRepository.saveAll(stateList);
};
}
}
実行する
FrondEnd:
npm start
BackEnd:
参考
ReactJS hooks: https://reactjs.org/docs/hooks-intro.html
React Redux: https://react-redux.js.org/introduction/getting-started
Spring Boot2: https://spring.io/