게시판 프로젝트를 하는 이유?!
단순한 CRUD 성의 프로젝트지만, Spring Boot 3와 React를 연동하고 결과물을 내는데 의의가 있습니다.
가벼운 프로젝트지만 여러가지를 내포하고 있습니다.
- Java: 17
- Spring Boot: 3.3.2
- Build Tool: Gradle
- Node: 20.15.1
- React: 18.3.1
- IDE: IntelliJ, WebStorm
위의 환경에서 개발하였습니다.
⚠️ 버전에 따라 코드가 다를 수 있습니다.
개발환경 세팅 먼저 시작해보도록 하겠습니다~!
Spring Initializer를 통해 프로젝트를 생성합니다.
새 프로젝트 | 라이브러리 추가 |
---|---|
Project 초기화면입니다.
application.properties를 application.yml로 확장자를 변경하고, 아래와 같이 환경설정 정보를 넣어주었습니다.
spring:
application:
name: board
# DataSource Configuration
datasource:
url: jdbc:postgresql://127.0.0.1:5432/board
username: postgres
password:
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
hibernate:
ddl-auto: create-drop
devtools:
restart:
enabled: true
server:
port: 8080
logging:
level:
org.springframework.web: info
프론트 연동을 테스트 하기 위해 현재 시간을 가져오는 간단한 API를 추가하였습니다.
ApiController 클래스를 생성하였습니다.
package io.github.twinklekhj.board.api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@RestController
@Slf4j
public class ApiController {
@GetMapping("/api/time")
public ResponseEntity<String> getTime() {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return ResponseEntity.ok().body(LocalDateTime.now().format(dtf));
}
}
터미널을 열어 React 프로젝트를 생성합니다.
저는 TypeScript 기반 프로젝트를 CRA로 생성하였습니다.
cd src/main
npx create-react-app frontend --template typescript
PS D:\Git\board\src\main> npx create-react-app frontend
Creating a new React app in D:\Git\board\src\main\frontend.
Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts
with ccra-template-typescript...
Front IDE를 열어 프로젝트를 실행해줍니다.
(저는 WebStorm을 이용하였습니다.)
http://localhost:3000 로 접속할 수 있습니다.
fetch 메소드를 이용하여 API를 호출하는 코드를 예시로 작성해보았습니다.
// 파일 위치: src/App.tsx
import React, {useEffect} from 'react';
const App = () => {
const [time, setTime] = React.useState("");
useEffect(() => {
fetch("http://localhost:8080/api/time", {
method: "GET"
})
.then(res => {
if (!res.ok) {
throw new Error('Network response was not ok');
}
return res.text();
})
.then(res => {
setTime(res)
})
.catch(err => console.error(err));
}, [])
return (
<div>
Hi, Server time is {time}
</div>
);
};
export default App;
냅다 호출코드에 url을 적으면 문제가 발생하리란 걸 아실 겁니다.
바로 CORS 문제!!
CORS란?
CORS(Cross-Origin Resource Sharing)는 직역하면 "교차 출처 리소스 공유"로 URL에서 도메인만 뜻하는 게 아니라 프로토콜과 포트까지 포함하는 개념입니다.
출처를 구성하는 세 요소는 프로토콜·도메인(호스트 이름)·포트로, 이 중 하나라도 다르면 CORS 에러를 만나게 됩니다.
React에서 다른 애플리케이션을 호출할 때 생기는 CORS를 해결하기 위해서 Proxy 설정이 꼭 필요합니다.
React에서는 두가지 방법을 제공합니다.
package.json에 프로퍼티를 추가하는 방법은 간단합니다.
아래 명령어를 통해 http-proxy-middleware 모듈을 설치해줍니다.
npm install http-proxy-middleware --save
npm install @types/http-proxy-middleware
아래와 같이 설정합니다.
.env
파일 생성setupProxy.js
파일 생성파일을 넣기만 하면 자동으로 설정합니다.
주의! ts 파일로 생성하면 자동으로 생성되지 않습니다.
// 파일 위치: frontend/.env
REACT_APP_API_URL=http://localhost:8080/api
// 파일위치: frontend/src/setupProxy.js
import { createProxyMiddleware } from 'http-proxy-middleware';
const { REACT_APP_API_URL } = process.env;
module.exports = function (app: any) {
app.use(
'/api',
createProxyMiddleware({
target: REACT_APP_API_URL || 'http://localhost:8080/api',
changeOrigin: true,
})
);
};
이제 fetch 코드에서 api url을 빼줍니다.
fetch("/api/time", {
method: "GET",
})
.then(res => {
if (!res.ok) {
throw new Error('Network response was not ok');
}
return res.text();
})
.then(res => {
setTime(res)
})
.catch(err => console.error(err));
Spring Boot 빌드 배포시 자동으로 react 파일을 import 하도록 배포 자동화를 설정할 수 있습니다. build.gradle 파일을 열어 하단에 아래 내용을 추가해줍니다.
processResources {
dependsOn "copyReactBuildFiles"
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
// react 설치
tasks.register('installReact', 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'
}
}
// react 빌드
tasks.register('buildReact', 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"
}
}
// react build 파일 static 폴더로 복사
tasks.register('copyReactBuildFiles', Copy) {
dependsOn "buildReact"
from "$frontendDir/build"
into "$project.projectDir/src/main/resources/static"
}
Spring Boot 프로젝트가 build 될 때 실행시키는 스크립트 입니다.
Spring 프로젝트를 실행만 하더라도 아래와 같이 화면이 구성된 것을 볼 수 있습니다.
이것으로 환경 설정이 마무리 되었습니다!! 다음 포스팅에서는 JWT를 이용한 Spring + React 로그인에 대해 포스팅하겠습니다🙌