서비스 통합
- 크로스오리진 리소스 셰어링
- 스프링 @Configuration 을 이용한 CORS 문제해결
- fetch를 이용한 프론트엔드와 백엔드 통합
현재 저는 독립적으로 동작하는 백엔드 애플리케이션과 독립적으로 동작하는 프론트엔드 어플이 하나씩있습니다!
이제는 두 어플리케이션을 통합해 하나의 기능을 하는 웹 애플리케이션을 완성할 차례입니다!
이제는 프론트엔드에서 백엔드에 API를 요청하는 코드를 작성해보겠습니다
우리는 이제 JS의 fetch API를 이용해서 Todo 아이템 CRUD를 하게 할게요!
하지만 이때 CORS문제에 만닥뜨리게 된다. 이 문제는 보안과 관련되어 있어 백엔드에서 해결해 줘야합니다.
우리가 개발하는 애플리케이션을 통해 CORS가 무엇이고 이를 어떻게 해주어야할까요!?
App컴포넌트에 componentDidMount 추가
App.js에
componentDidMount(){
const requestOptions = {
method : "GET",
headers : { "Content-Type" : "application/json"},
};
fetch("http://localhost:8080/todo",requestOptions)
.then((response)=>response.json())
.then(
(response)=>{
this.setState({
items: response.date,
});
},
(error)=>{
this.setState({
error,
});
}
);
}
를 추가해 주었다.
그리고 localhost3000으로 들어가 개발자 툴의 콘솔창을 켜보면 에러를 확인할 수 있습니다!
Access to fetch at 'http://localhost:8080/todo' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
이는 보안을 위한 CORS 헤더 Policy를 위반했기 때문이다.
CORS
CORS는 크로스 오리진 리소스 셰어링 의 약자로, 처음 리소스를 제공한 도메인이 현재 요청하려는
도메인과 다르더라도 요청을 허락해 주는 웹 보안 방침입니다. 나중에 추후 설명드리겠습니다!
백엔드단에 파일을 추가해주어야 합니다!
CORS설정을 위해 WebMvcConfig를 만들어봅시다
패키지를 config로, 이름을 WebMvcConfig로 지어줍니다!
package com.unoSpringBoot.study.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 스프링 빈으로 등록
public class WebMvcConfig implements WebMvcConfigurer {
private final long MAX_AGE_SECS = 3600;
@Override
public void addCorsMappings(CorsRegistry registry) {
// 모든 경로에 대해
registry.addMapping("/**")
// Origin이 http:localhost:3000에 대해
.allowedOrigins("http://localhost:3000")
// 겟,포스트,풋 등등 메서드를 허용한다.
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS").allowedHeaders("*")
.allowCredentials(true).maxAge(MAX_AGE_SECS);
}
}
해당 메서드에서 작성한 설정은
모든 경로에 대해 오리진이 해당 로컬호스트주소인경우
겟, 포스트 등등 메서드를 이용한 요청을 허용한다.! 또 모든 헤더와 인증에 관한 정보도 허용한다
라는 의미입니다

지금과 같은 코드를 넣어주고 시작해보았다.
스프링부트 서버를 시작해주고
로컬호스트 3000 을 새로고침을 해주었더니 갑자기 에러가 발생한다????

하지만!!! 스프링부트 콘솔창에는 인식을 하고있다... 휴...
프론트엔드 쪽에서 이런 연결을 위한 js파일을 만들어주자!
js폴더안에 api-config라고 생성했다.
let backendHost;
const hostName = window && window.location && window.location.hostName;
if(hostName ==="localhost"){
backendHost = "http://localhost:8080";
}
export const API_BASE_URL =`${backendHost}`;
ㅇ백엔드로 요청을 보낼때 사용할 유틸리티 함수들을 작성할 js파일도 만들어야합니당!
service기능을할테니 js폴더안에 service라고 폴더를 새로 생성한다음
ApiService.js라고 만든뒤 내용을 채워넣어줍시다~!!
import { API_BASE_URL } from "../api-config";
export function call(api, method, request) {
let options = {
headers: new Headers({
"Content-Type": "application/json",
}),
url: API_BASE_URL + api,
method: method,
};
if (request) {
// GET method
options.body = JSON.stringify(request);
}
return fetch(options.url, options).then((response) =>
response.json().then((json) => {
if (!response.ok) {
// response.ok가 true이면 정상적인 리스폰스를 받은것, 아니면 에러 리스폰스를 받은것.
return Promise.reject(json);
}
return json;
})
);
}
이제 App.js 기존 함수들을 ApiService의 메서드를 이용해 수정해보겠습니다!!!
componentDidMount() {
call("/todo", "GET", null).then((response) =>
this.setState({ items: response.data })
);
}
add = (item) => {
call("/todo", "POST", item).then((response) =>
this.setState({ items: response.data })
);
};
delete = (item) => {
call("/todo", "DELETE", item).then((response) =>
this.setState({ items: response.data })
);
};
update = (item) => {
call("/todo", "PUT", item).then((response) =>
this.setState({ items: response.data })
);
};
기존의 add, delete, update를 모두 같게하고 call안의 매개변수값만 바꿔주었다!
그리고 조금 수정했다. "/todo"를 전역변수 var todoURL로 관리하여 코드수정이 용이하게 하였다.
이제 정상적으로 웹단에서 돌아가는지 확인해보자!
자기소개를 했습니다. 과연 새로고침을 하면 사라질까요...?!

그대롭니다!!
백엔드의 데이터베이스에서 Todo리스트를 가져오므로 새로고침을 해도 사라지지 않습니다!
또 프론트엔드를 완전 종료했다가 다시켜도 사리지지않습니다.
매끄럽게 수정시키기
수정을 매끄럽게 해주기 위해서 기존코드를 수정해야합니다.
update = (item) => {
call(todoURL, "PUT", item).then((response) =>
this.setState({ items: response.data })
);
};
는 아까 추가해주었지만 다시 한번 말씀을 드리겠습니다. 해당 update를 했다면, 새로 리스트화가 되어주어야 하기때문에
App.js의 렌더 부분의 Todo에 props를 연결해주어야합니다.
render() {
var todoItems = this.state.items.length > 0 && (
<Paper style={{ margin: 16 }}>
<List>
{this.state.items.map((item, idx) => (
<TodoList
item={item}
key={item.id}
delete={this.delete}
update={this.update}
/>
))}
</List>
</Paper>
);
return (
<div className="App">
<Container maxWidth="md">
<AddTodo add={this.add} />
<div className="TodoList">{todoItems}</div>
</Container>
</div>
);
}
}
연결해줍니다.
그리고 todo.js의 컨스트럭트 부분에
constructor(props) {
super(props);
this.state = { item: props.item,readOnly : true};
this.delete = props.delete;
this.update = props.update; //update를 this.update에 할당
}
를 추가해줍니다.
그리고 대망의 이벤트핸들러에게 저 update를 넣어줍니다.
checkboxEventHandler = (e) => {
const thisItem = this.state.item;
thisItem.done = !thisItem.done;
this.setState({ item: thisItem });
this.update(this.state.item); //체크박스가 변경되면 저장
};
enterKeyEventHandler = (e) => {
if (e.key === "Enter") {
this.setState({readOnly: true});
this.update(this.state.item); //엔터누르면 저장
}
};
그럼 작동을 잘하는지 확인해봅시다!
수정전인상태입니다~!
이렇게 요청이 성공했다면 성공적으로 업데이트가 된것입니다! 이렇게 수정까지 해 서 로컬환경에서 동작하는 완전한 Todo 애플리케이션을 완성했습니다!!!
'Spring boot 프로젝트 기록 > 2. 프론트엔드 개발' 카테고리의 다른 글
로그인 페이지 (0) | 2021.12.15 |
---|---|
로그인 컴포넌트 (0) | 2021.12.14 |
Todo 수정 (0) | 2021.12.01 |
Todo 기능(추가, 삭제) (0) | 2021.12.01 |
리액트로 간단한 컴포넌트 만들어보기 (0) | 2021.11.30 |
댓글