리액트 스프링부트 axios 연동 안되는 문제 해결
사용 IDE : 인텔리제이
사용 DB : MariaDB
DB 이름 : board
Java버전 : 11
node.js : 설치됨(npm 명령어를 사용하기 위해 설치하는 것이 좋습니다.)
블로그 글 수십 개 뒤져서 겨우 해냈습니다.. 겪었던 오류들 총 정리해서 모아봤습니다.
1. 스프링부트 프로젝트 생성
설정을 마치고 GENERATE 버튼을 누르면 다운로드 폴더에 압축된 폴더가 저장됩니다.
압축을 풀어주시고 폴더 안에 들어가면 같은 이름의 폴더가 또 나옵니다.
이걸 복사해서 C드라이브에 붙여 놓겠습니다.
자바 버전과 스프링 부트 버전이 호환이 되는지 확인하셔야 합니다.
3.0.n대 스프링 부트 버전을 쓰는데 자바가 11이하일 경우에 에러가 발생했습니다.
2. 인텔리제이에서 불러오기
인텔리제이를 실행하고 C드라이브에 저장했던 폴더를 Open합니다.
프로젝트를 열면 지가 알아서 필요한 파일을 다운받기 시작합니다.
src/main/java/com.study.board1(설정했던 패키지 이름)/내부에 있는 자바 파일을 열어봅시다.
다운이 다 되었다면 우측의 초록색 삼각형 버튼이 생겨날 것입니다. 클릭해 봅시다.
Failed to determine a suitable driver class 에러가 발생했다면 아래와 같이 해결합니다.
(원인 : 스프링부트는 어플리케이션이 시작될 때 필요한 기본 설정들을 자동으로 설정하게 되어있는데, 그중에 DataSource 설정이 자동구성 될 때 필요한 데이터베이스 정보가 설정되지 않아 발생하는 문제라고 합니다.
appliction.properties 에 사용자가 원하는 DB 설정을 넣고, 맞는 드라이버와 라이브러리 설치, JDBC 설정을 해주어야 합니다.)
src/main/resources/application.properties를 클릭합니다.
아래 내용을 붙여 넣습니다.
(사용할 db의 종류와 정보에 맞게 변경하셔야 합니다! 다른 DB를 사용하실 경우에는
구글에 Failed to determine a suitable driver class 에러를 검색해서 해당하는 DB의 양식을 찾아보시는 것이 좋습니다.
mariaDB, DB이름 = board, 계정이름 = root, 비밀번호 = 1234일경우에는 다음과 같습니다.)
server.port=8080
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver spring.datasource.url=jdbc:mariadb://localhost:3306/board
spring.datasource.username=root
spring.datasource.password=1234
(만약 당장 JDBC설정이 필요없고 어떤 DB를 사용할지 결정하지 않았다면
스프링부트 메인 클래스에 다음과 같은 어노테이션을 추가해주고 다시 실행하시면 해결 됩니다.)
@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
위의 문제를 해결하고 다시 실행했을때 localhost:8080 에서 아래와 같은 화면이 나타나고 터미널에 별 이상한 점이 발견되지 않다면 정상적으로 실행된 것입니다.
다음과 같은 자바 버전 에러 문제가 발생한다면, 환경 변수 설정에 자바 버전이 11이 맞는지, 프로젝트에 설정된 자바 버전이 11이 맞는지 확인을 해주셔야 합니다.
File - Settings - Build, Execution, Deployment - Build Tools - Gradle
Gradle로 프로젝트를 실행하는 것보다 인텔리제이 IDEA로 실행하는 것이 더 빠르기 때문에 아래 세팅으로 맞춰줍니다. 여기서 Gradle JVM의 자바 버전을 11로 맞춰 주었습니다.
File - Proejct Structure
Project와 SDKs 탭에 있는 자바 버전도 맞춰봅니다.
그래도 버전 에러가 생기면 환경 변수를 확인해 봅시다.
윈도우에 '환경'이라고 입력하면 '시스템 환경 변수 편집'이 나타납니다.
여기서 지정된 자바 버전과 스프링 부트 환경설정시에 맞추었던 자바 버전이 맞지 않을 때도 있습니다. 같도록 맞춰 줍니다.
그래도 안되는 경우에는 스프링 부트 버전과 자바 버전이 호환되지 않아서 발생하는 문제일 수도 있습니다.
스프링 부트의 버전이 3.0.n인 경우에는 자바 17이상을, 2.n.n인 경우에는 11이하로 맞춰주시면 해결됩니다.
3. React 설치
터미널을 열어봅니다.
src/main의 경로에 리액트를 설치해줍시다.
cd src/main
npx create-react-app frontend
설치가 완료되면 경로 구성이 이렇게 됩니다.
frontend/src를 까보면 App.js파일이 생성되었음을 확인할 수 있습니다.
터미널에서 리액트를 실행해 봅니다.
cd frontend
npm start
4. 스프링부트와 리액트 연동
먼저 CORS 매커니즘에 대해서 알아봅시다.
CORS (Cross Origin Resource Sharing)
서버와 클라이언트가 동일한 IP주소에서 동작하고 있다면, resource를 제약 없이 서로 공유할 수 있지만, 만약 다른 도메인에 있다면 원칙적으로 어떤 데이터도 주고받을 수 없도록 하는 매커니즘입니다.
리액트는 3000포트 번호에서 동작하고 있는 반면, 스프링 부트는 8080에서 동작했습니다.
도메인이 다르기 때문에 CORS에 의해 데이터를 주고 받을 수 없게 됩니다.
이 문제를 해결하기 전에 먼저 필요한 코드를 생성해 봅니다.
백엔드와 프론트 엔드 사이의 통신을 쉽게 하기위해 axios 라이브러리가 사용됩니다.
frontend폴더에 axios를 설치해봅시다.
인텔리 제이로 돌아가면 리액트 앱이 실행되고 있는 중이라 터미널 사용이 안되는 것을 확인할 수 있습니다.
이때 Ctrl+C를 눌러서 원래 상태로 되돌리면 됩니다.
npm install axios --save
리액트가 설치된 frontend폴더에 axios를 설치해야합니다. 엉뚱한 경로에 설치하면 axios import문제가 발생할 수 있습니다.
src/main/frontend/src/App.js에 다음과 같은 코드를 입력합니다.
import React, {useEffect, useState} from 'react';
import axios from 'axios';
function App() {
const [msg, setMsg] = useState('')
useEffect(() => {
axios.get('/hello')
.then(response => setMsg(response.data))
.catch(error => console.log(error))
}, []);
return (
<div>
백엔드 통신 성공? : {msg}
</div>
);
}
export default App;
백엔드의 /hello url경로로부터 값을 받아 msg값을 setState로 변경하고 변경된 msg값을 프론트 상에 뿌려주는 코드입니다.
java/com.study.board1 경로에 Controller 패키지를 만들고, 그 안에 TestController.java 파일을 만듭니다.
그리고 아래의 코드를 입력해 주었습니다.
package com.study.board1.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/hello")
public String hello() {
return "YESYESYES";
}
}
이제 스프링 부트 프로젝트를 먼저 빌드하고, 터미널에서 frontend 폴더 경로로 이동해 npm start 명령어로 리액트까지 실행시켜줍니다.
통신 실패했습니다. YESYESYES가 나타나야하는데 f12를 눌러 개발자 도구를 열어보니 AxiosError가 발생했음을 확인할 수 있었습니다.
위에서 언급한 CORS 메커니즘으로 인해 리액트와 스프링부트의 도메인이 달라서 통신이 제대로 이루어지지 않았기 때문입니다.
다행히 프록시 설정을 맞춰줌으로써 이 문제를 해결할 수 있습니다.
저는 http-proxy-middelware 라이브러리를 활용하였습니다.
터미널을 열어서 src/main/frontend 경로로 맞춰줍니다.
npm install http-proxy-middleware --save
src/main/frontend/src 에서 setProxy.js 파일을 만들고 아래 코드를 입력해줍니다.
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/hello',
createProxyMiddleware({
target: 'http://localhost:8080', // 서버 URL or localhost:설정한포트번호
changeOrigin: true,
})
);
};
이제 프론트엔드에서 /hello로 요청을 보내면 백엔드인 8080포트(=target)로 요청이 도착하게 됩니다.
이제 스프링 부트 프로젝트를 먼저 빌드하고, 터미널에서 frontend 폴더 경로로 이동해 npm start 명령어로 리액트까지 실행시켜줍니다.
그래도 안되는 경우
src/main/frontend/src/package.json 파일에
"proxy": "http://localhost:8080",을 복사해서 붙여넣기 해줍니다.
(휴스턴-휴스턴-들리는가??)
localhost:3000에서 localhost:8080/hello로부터 데이터를 요청해 받아올 수 있게 되었습니다. 만약 여러 사람과 분업이 되어 있는 경우에는 그에 맞는 개발 서버로 proxy를 연결해주어야 합니다.
5. 빌드
build.gradle 하단부에 코드를 추가합니다.
스프링부트 프로젝트가 build될 때 React 프로젝트가 먼저 build되고, 결과물을 스프링부트 프로젝트 build 결과물에 포함시킨다는 스크립트입니다. 이렇게 하면 localhost:8080에서 위와 같은 결과를 받아볼 수 있습니다.
def frontendDir = "$projectDir/src/main/frontend"
sourceSets {
main {
resources { srcDirs = ["$projectDir/src/main/resources"]
}
}
}
processResources { dependsOn "copyReactBuildFiles" }
task installReact(type: Exec) {
workingDir "$frontendDir"
inputs.dir "$frontendDir"
group = BasePlugin.BUILD_GROUP
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
commandLine "npm.cmd", "audit", "fix"
commandLine 'npm.cmd', 'install' }
else {
commandLine "npm", "audit", "fix" commandLine 'npm', 'install'
}
}
task buildReact(type: Exec) {
dependsOn "installReact"
workingDir "$frontendDir"
inputs.dir "$frontendDir"
group = BasePlugin.BUILD_GROUP
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
commandLine "npm.cmd", "run-script", "build"
} else {
commandLine "npm", "run-script", "build"
}
}
task copyReactBuildFiles(type: Copy) {
dependsOn "buildReact"
from "$frontendDir/build"
into "$projectDir/src/main/resources/static"
}
우측에 Load Gradle Changes를 클릭하고 기다립니다.
홈 디렉토리로 빠져나와 빌드를 진행합니다.
BUILD SUCCESSFUL 메시지가 뜨면 아래의 경로에 jar 파일이 생성된 것을 확인할 수 있습니다.
이제 스프링부트 서버를 실행시키고 localhost:8080으로 가면 아까와 같은 화면을 받아볼 수 있습니다.
cmd창에서 이 파일을 실행시켜도 같은 화면을 받아볼 수 있습니다.
프로젝트가 있는 경로로 이동하고 java -jar [jar 파일명.jar]로 서버를 실행합니다.
localhost:8080을 열어서 확인해 보면 같은 화면이 나타납니다.
이렇게 생성된 jar파일을 Spring boot의 static 폴더에 넣어서 Spring boot를 빌드하는 식으로 백엔드 개발자가 리액트 개발자와 협업을 할 수 있겠습니다. 대신 이렇게 빌드를 하고나면 백엔드 로직을 바꿨을 때 재빌드를 해줘야하는 번거로움이 있습니다. 협업을 위해서는 React와 Springboot를 분리해서 운영하고, React측에서 서버로부터 JSON파일을 받아오는 방식으로 운영하는 것이 나아보입니다.
도움이 되셨으면 좋겠습니다!
*추가
Controller에서 json형태로 넘겨주기
TestController.java를 아래와 같이 입력해봅니다.
package com.study.board1.Controller;
import com.study.board1.TestApi;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
@GetMapping("/hello")
@ResponseBody
public TestApi test(){
TestApi testApi = new TestApi();
testApi.setId("test");
testApi.setPassword("password");
return testApi;
}
}
@ResponseBody 어노테이션은 객체를 JSON형태로 매핑해주는 기능을 가지고 있습니다.
다만 이 방식은 Controller 안에 메서드가 많아지면 각각의 메서드마다 @ResponseBody 어노테이션을 다 달아줘야하는 번거로움이 있습니다.
그래서 요즘은 @Controller대신 @RestController를 쓰는 방법을 사용한다고 합니다.
@Controller + @ResponseBody 라고 생각하면 편합니다.
package com.study.board1.Controller;
import com.study.board1.TestApi;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/hello")
public TestApi test(){
TestApi testApi = new TestApi();
testApi.setId("test");
testApi.setPassword("password");
return testApi;
}
}
패키지 내에 TestApi.java 파일을 만들고 아래와 같이 코딩해 주었습니다. JSON형태로 매핑할 객체를 만드는 것입니다.
package com.study.board1;
public class TestApi {
private String id;
private String password;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
인텔리제이의 alt+insert 단축키를 활용하면 Getter&Setter 메서드를 쉽게 만들어 낼 수 있습니다.
App.js 내용도 단순하게 바꿔보겠습니다.
import React, {useEffect, useState} from 'react';
import axios from 'axios';
function App() {
const [msg, setMsg] = useState('')
useEffect(() => {
axios.get('/hello')
.then(response => setMsg(response.data))
.catch(error => console.log(error))
}, []);
return (
<div>
{msg}
</div>
);
}
export default App;
백엔드 로직을 바꾸었기 때문에 새로 빌드를 해주고 localhost:8080/hello를 접속합니다.
자동으로 매핑된 JSON값을 받아오게 되었습니다! 와!
'문제 해결' 카테고리의 다른 글
티스토리 블로그 게시글 간단하게 목차 만들기, 목차로 되돌아가는 링크 만들기(개쉬움주의) (0) | 2023.02.14 |
---|---|
VSCODE 화면 그래픽 깨짐 현상 문제 해결 (0) | 2023.01.14 |
[MySQL] Access denied for user '유저아이디'@'localhost' (using password: YES) 에러 문제 해결 방법 (0) | 2022.11.16 |
[MySQL] MySQL installer 포스번호 설정 오류 the specified port already in use (0) | 2022.11.02 |
인텔리제이(Intelij) 한글 깨짐 인코딩 에러 해결 방법 (0) | 2022.04.02 |