TodoList 프로젝트 - SpringBoot&React
84051 단어 todoListReactSpringbootReact
todolist-backend
- build.gradle dependencies
web, jpa, mysql, lombok 디팬더시 적용
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
compileOnly 'mysql:mysql-connector-java'
implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.26'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
- application.yml
mysql username, password 은 사용자 임의대로 만들면 된다.
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/todolist
username: todolist
password: todolist
jpa:
open-in-view: true
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
use-new-id-generator-mappings: false
show-sql: true
properties:
hibernate.format_sql: true
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
logging:
level:
org.hibernate.SQL: debug
todolist Model(TodoEntity)
- TodoEntity
package com.example.todolist_prac.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table(name = "Todo")
public class TodoEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column(name = "todoOrder", nullable = false)
private Long order;
@Column(nullable = false)
private Boolean completed;
}
실제 run을 해보면서 mysql에 이름이 Todo
인 스키마가 존재하는지 확인해본다.
- TodoRequest
package com.example.todolist_prac.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TodoRequest {
private String title;
private Long order;
private Boolean completed;
}
- TodoResponse
package com.example.todolist_prac.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodoResponse {
private Long id;
private String title;
private Long order;
private Boolean completed;
public TodoResponse(TodoEntity todoEntity) {
this.id = todoEntity.getId();
this.title = todoEntity.getTitle();
this.order = todoEntity.getOrder();
this.completed = todoEntity.getCompleted();
}
}
TodoEntity -> TodoResponse 가 되는 메서드를 만들었다.
Repository, Service, Controller
- TodoRepository
package com.example.todolist_prac.repository;
import com.example.todolist_prac.model.TodoEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TodoRepository extends JpaRepository<TodoEntity, Long> {
}
- TodoService
package com.example.todolist_prac.service;
import com.example.todolist_prac.model.TodoEntity;
import com.example.todolist_prac.model.TodoRequest;
import com.example.todolist_prac.repository.TodoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Service
@RequiredArgsConstructor
public class TodoService {
public final TodoRepository todoRepository;
// 추가
public TodoEntity add(TodoRequest todoRequest) {
TodoEntity todoEntity = getTodoEntity(todoRequest);
return todoRepository.save(todoEntity);
}
// add method refactoring
private TodoEntity getTodoEntity(TodoRequest todoRequest) {
return TodoEntity.builder()
.title(todoRequest.getTitle())
.order(todoRequest.getOrder())
.completed(todoRequest.getCompleted())
.build();
}
// 조회
public TodoEntity searchById(Long id) {
var byId = todoRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return byId;
}
// 전체 조회
public List<TodoEntity> searchAll() {
var all = todoRepository.findAll();
return all;
}
// update1
public TodoEntity updateById(Long id) {
TodoEntity todoEntity = this.searchById(id);
todoEntity.setCompleted(true);
return todoRepository.save(todoEntity);
}
// update2
public TodoEntity updateById(Long id, TodoRequest request) {
TodoEntity todoEntity = this.searchById(id);
if (request.getTitle() != null) {
todoEntity.setTitle(request.getTitle());
}
if (request.getOrder() != null) {
todoEntity.setOrder(request.getOrder());
}
if (request.getCompleted() != null) {
todoEntity.setCompleted(request.getCompleted());
}
return todoRepository.save(todoEntity);
}
// 삭제
public void deleteById(Long id) {
todoRepository.deleteById(id);
}
// 전체 삭제
public void deleteAll() {
todoRepository.deleteAll();
}
}
- TodoController
package com.example.todolist_prac.controller;
import com.example.todolist_prac.model.TodoEntity;
import com.example.todolist_prac.model.TodoRequest;
import com.example.todolist_prac.model.TodoResponse;
import com.example.todolist_prac.service.TodoService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@CrossOrigin
@AllArgsConstructor
@RestController
@RequestMapping("/todo")
public class TodoController {
@Autowired
private final TodoService service;
@PostMapping
public ResponseEntity<TodoResponse> create(@RequestBody TodoRequest request) {
log.info("Create");
if (ObjectUtils.isEmpty(request.getTitle())) {
return ResponseEntity.badRequest().build();
}
if (ObjectUtils.isEmpty(request.getOrder())) {
request.setOrder(0L);
}
if (ObjectUtils.isEmpty(request.getCompleted())) {
request.setCompleted(false);
}
TodoEntity result = this.service.add(request);
return ResponseEntity.ok(new TodoResponse(result));
}
@GetMapping("{id}")
public ResponseEntity<TodoResponse> readOne(@PathVariable Long id) {
log.info("Read One");
TodoEntity result = this.service.searchById(id);
return ResponseEntity.ok(new TodoResponse(result));
}
@GetMapping
public ResponseEntity<List<TodoResponse>> readAll() {
log.info("Read All");
List<TodoEntity> list = this.service.searchAll();
List<TodoResponse> responses = list.stream()
.map(TodoResponse::new)
.collect(Collectors.toList());
return ResponseEntity.ok(responses);
}
@PutMapping("{id}")
public ResponseEntity<TodoResponse> update(@PathVariable Long id) {
log.info("Update");
TodoEntity result = this.service.updateById(id);
return ResponseEntity.ok(new TodoResponse(result));
}
@DeleteMapping("{id}")
public ResponseEntity<?> deleteOne(@PathVariable Long id) {
log.info("Delete");
this.service.deleteById(id);
return ResponseEntity.ok().build();
}
@DeleteMapping
public ResponseEntity<?> deleteAll() {
log.info("Delete All");
this.service.deleteAll();
return ResponseEntity.ok().build();
}
}
WebConfig
cors 허용
을 위해 만들었다.
package com.example.todolist_prac.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins("http://localhost:3000") // 이 주소는 cors 허용
.allowedMethods(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name()
);
}
}
todolist-front
React
사용, npm
설치 후 실행하자.
- Create React App
npx create-react-app my-app
cd my-app
npm start
- package.json
{
"name": "todolist-front",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"axios": "^0.24.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
- index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</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();
- App.js
import React, {useState, useEffect} from 'react'
import axios from 'axios'
import './App.css';
function App() {
const baseUrl = "http://localhost:8080"
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
useEffect(() => {
getTodos();
}, []) // [] 리액트 열렸을 때 한번만 실행하는 게 하는 것!
async function getTodos(){
await axios // 다 받을 때까지 기다리는 것
.get(baseUrl + "/todo")
.then((res) => {
console.log(res.data)
setTodos(res.data)
})
.catch((err) => {
console.log(err)
})
}
function insertTodo(e){
e.preventDefault()
const insertTodo = async() => {
await axios
.post(baseUrl + "/todo", {
title: input
})
.then((res) => {
console.log(res.data)
setInput("")
getTodos()
})
.catch((err) => {
console.log(err)
})
}
insertTodo()
console.log("할일이 추가되었습니다.")
}
function updateTodo(id){
const updateTodo = async() => {
await axios
.put(baseUrl + "/todo/" + id, {})
.then((res) => {
console.log(res.data)
// getTodos() 굳이 db에 더 조회하지 말자!
// 화면에서 바꾸자
setTodos(
todos.map((todo) =>
todo.id === id ? {...todo, completed: !todo.completed} : todo
)
)
})
.catch((err) => {
console.log(err)
})
}
updateTodo()
}
function deleteTodo(id){
const deleteTodo = async() => {
await axios
.delete(baseUrl + "/todo/" + id, {})
.then((res) => {
console.log(res.data)
setTodos(
todos.filter((todo) => todo.id !== id)
)
})
.catch((err) => {
console.log(err)
})
}
deleteTodo()
}
function changeText(e){
e.preventDefault()
setInput(e.target.value)
console.log(input)
}
return (
<div className="App">
<h1>TODO LIST</h1>
<form onSubmit={insertTodo}>
<label>
Todo
<input type="text" required={true} value={input} onChange={changeText}/>
</label>
<input type="submit" value="Create" />
</form>
{
todos
? todos.map((todo) => {
return (
<div className="todo" key={todo.id}>
<h3>
<label
className= {todo.completed ? "completed" : null}
onClick={() => updateTodo(todo.id)}>
{todo.title}
</label>
<label onClick={() => deleteTodo(todo.id)}> ❌</label>
</h3>
</div>
)
})
: null
}
</div>
);
}
export default App;
- App.css
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.completed {
text-decoration: line-through;
}
출처
Author And Source
이 문제에 관하여(TodoList 프로젝트 - SpringBoot&React), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@mooh2jj/todoList-프로젝트-설명저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)