LoginSignup
1
2

More than 1 year has passed since last update.

SpringBoot2とReactJSで簡単なTodoListを作成

Last updated at Posted at 2023-01-10

初めに

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

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

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

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を使う。
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

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:

npm start

Screenshot 2023-01-10 at 16.36.14.png

BackEnd:

Screenshot 2023-01-10 at 16.43.25.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/

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2