SpringBoot2とReactJSで簡単なTodoListを作成

# 初めに
はじめまして。
今回、学習のため、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。

# 結果
https://drive.google.com/file/d/1wxhPNwDdoERaSb-lpPZ0GdI3iAKZnZK-/view?usp=share_link
# 作製方法
■準備
- Install NodeJS（最新バーション）
- Install ReactJS 
- Install SDK(投稿で使われるのはcorretto-17)
- Install MySql Server

■FrontEnd
- Github Link: https://github.com/TuanAnh-Dao/todolist/tree/master/frontend
- ディレクトリ構成
![Screenshot 2023-01-10 at 14.11.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741420/d94c871c-07ec-e600-f0b7-54566f533243.png)

## index.js

```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

```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;

```

![Screenshot 2023-01-10 at 13.56.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741420/996601cb-e52c-19a5-350e-4f9c6a200369.png)

groupSlide.js ではactions, reducers, Storeの構造を定義する。

## Configuration.js
``` 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
- ディレクトリ構成
![Screenshot 2023-01-10 at 15.19.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741420/0d0a1c91-ca49-4be7-62f2-f2c3c13e99e1.png)

## pom.xml
``` 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を使う。
    １．spring-boot-starter-data-jpa：https://spring.io/projects/spring-data-jpa
    ２．spring-boot-starter-web：https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
    ３．mysql-connector-java：https://mvnrepository.com/artifact/mysql/mysql-connector-java
    ４．spring-boot-starter-security：https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security
    ５．lombok：https://www.baeldung.com/intro-to-project-lombok
## application.yml
```resources/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
```main/java/com/todolist/api/security/config/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
``` main/java/com/todolist/api/entity/State.java
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
```main/java/com/todolist/api/entity/Task.java
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の作成
```main/java/com/todolist/api/repository/StateRepository.java
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の作成
```main/java/com/todolist/api/service/StateService.java
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の作成
```main/java/com/todolist/api/controller/StateController.java
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の作成
```main/java/com/todolist/api/configuration/DefaultConfig.java
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: 
```cmd
npm start
```
![Screenshot 2023-01-10 at 16.36.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741420/6657ad2f-bc7e-b6c9-2d9a-9f897df0a824.png)

## BackEnd:

![Screenshot 2023-01-10 at 16.43.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741420/d58c3959-3028-7a0c-ef74-9fe435d51d25.png)

# 参考
ReactJS hooks: https://reactjs.org/docs/hooks-intro.html
React Redux: https://react-redux.js.org/introduction/getting-started
Spring Boot2: https://spring.io/