기존 프로젝트를 모바일 웹 환경에서도 제공하기 위해 React를 도입하게 되었다. 이에 따라 기존 src/static/templates 경로에서 관리하던 파일들을 React 형식에 맞게 변경해야 했다. 백엔드와 프론트엔드 간 데이터 흐름을 명확히 이해하고자 정리한 내용이다.
먼저 Spring Boot 서버를 실행
백엔드 서버가 정상적으로 동작하고 있어야, React에서 API 요청을 보냈을 때 올바른 응답을 받을 수 있다.
이후, 새로운 터미널 창을 열어 React 프로젝트 폴더로 이동한다.
cd [리액트 폴더 경로]
터미널 경로가 올바르게 변경된 것을 확인한 뒤, React 개발 서버를 실행한다. npm start
명령어를 입력하면 자동으로 브라우저가 열리며 http://localhost:3000 포트에서 웹페이지가 실행된다.
React는 모바일 환경을 기준으로 개발 중이므로, 브라우저의 개발자 도구를 활용해 모바일 화면으로 전환해준다.
크롬 기준으로 F12를 눌러 개발자 도구를 연 후, 상단 메뉴에서 '디바이스 모드(📱)'로 전환하면 다양한 모바일 해상도로 화면을 확인할 수 있다.
React 프로젝트에서는 각 pages 폴더 안의 컴포넌트마다 개별 CSS 파일을 연결할 수 있지만, 공통 요소인 헤더(Header), 푸터(Footer) 등에 대한 스타일은 일반적으로 공유되는 CSS 파일에 정의한다.
예를 들어, Main.css에 헤더/푸터 스타일을 정의해두면, 해당 스타일은 pages 폴더 내부의 모든 페이지 컴포넌트에 전역적으로 적용된다.
@RestController 기능 : React에서 요청하면, Spring이 응답하거나 DB에 저장한다
@RestController는 JSON 형식으로 데이터를 주고받기 위한 API 컨트롤러이다.
@RestController
@RequestMapping("/api")
public class ShopApiController {
@GetMapping("/shops")
public List<ShopDto> getAllShops() {
return shopService.findAllShops(); // → DB에서 데이터 꺼내오기
}
@PostMapping("/shops")
public void addShop(@RequestBody ShopDto shopDto) {
shopService.save(shopDto); // → 프론트에서 받은 데이터 저장
}
}
-> 이런식으로 Controller/api 폴더 안에 만들어주면 된다.
다음과 같은 폴더 구조로 생겼다.
react-front/
├── src/
│ ├── pages/ // 화면별 컴포넌트(html이라고 생각) (예: MainPage.js, ShopListPage.js)
│ ├── service/ // API 호출 함수들 (백엔드랑 연결) -> 8080/api 랑 연결
│ ├── App.js // 전체 앱 라우팅, 구조 잡기 -> 3000번 포트에서 쓸 도메인 결정
pages/ 폴더: 각 페이지별 컴포넌트를 관리합니다.service/ 폴더: 백엔드 API와 통신하는 함수들을 정의합니다.App.js: React 앱의 라우팅을 설정하여 각 페이지 컴포넌트를 연결합니다.service/ — 백엔드와 통신하는 파일 (localhost:8080/api와 연결 시켜줌)기능: Spring API와 직접 통신. React에서는 이 함수를 호출해서 데이터를 주고받는다.
import axios from 'axios';
const BASE = 'http://localhost:8080/api';
export const fetchShopDetails = (id) =>
axios.get(`${BASE}/shop/${id}`, { withCredentials: true }).then(res => res.data);
export const toggleBookmark = (shopId) =>
axios.post(`${BASE}/bookmark/toggle`, { shopId }, { withCredentials: true }).then(res => res.data);
export const fetchBookmarkFolders = () =>
axios.get(`${BASE}/bookmark/folders`, { withCredentials: true }).then(res => res.data);
export const createBookmarkFolder = (name) =>
axios.post(`${BASE}/bookmark/folders`, { name }, { withCredentials: true }).then(res => res.data);
이런 식으로 BASE 경로를 지정한다(이 경로는 우리가 Controller/api 경로에 생성해준 controller와 연결해주면 된다)
fetchShopDetails와 같은 변수명은 추후에 pages/ 폴더에서 화면 연결하는 데에 사용된다.
pages/ — 실제로 화면에 데이터를 표시하는 컴포넌트우리가 8080에서 사용했던 html 파일을 react의 .js 파일 형식에 맞게 보여준다고 생각하면 된다.
import React, { useEffect, useState } from 'react';
import { fetchShops } from '../service/ShopApi';
function ShopListPage() {
const [shops, setShops] = useState([]);
useEffect(() => {
fetchShops().then(data => {
setShops(data);
});
}, []);
return (
<div>
<h1>가게 목록</h1>
<ul>
{shops.map(shop => (
<li key={shop.id}>{shop.name}</li>
))}
</ul>
</div>
);
}
export default ShopListPage;
→ 이런식으로 사용하는데 이때 앞서 우리가 8080포트와 연결할때 사용한 pages/(…)Api 파일과 연결시켜줘야한다.
App.js → 리액트 3000번 포트에서 사용할 도메인 지정예를 들어서 우리 8080/main경로로 들어가서 보여주는 화면을 리액트에서도 3000/main 으로 사용하고싶다!!
→ App.js 에서 설정해주면 된다. 이때 우리가 만든 리액트 프론트 화면인 pages 폴더와 연결하면 된다.
// src/App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Signup from './pages/Signup';
import Login from './pages/Login';
import Mypage from './pages/Mypage';
import Main from './pages/Main';
import MapPage from './pages/Map';
import BookmarkList from './pages/BookmarkList';
import LikeList from './pages/LikeList';
import FoodDetailsPage from './pages/FoodDetails';
import SearchPage from './pages/Search';
import BoardMain from './pages/BoardMain';
import BoardDetails from './pages/BoardDetails';
function App() {
return (
<Router>
<Routes>
<Route path="/member/new" element={<Signup />} />
<Route path="/member/login" element={<Login />} />
<Route path="/myPage" element={<Mypage />} />
<Route path="/main" element={<Main />} />
<Route path="/shopDetails/:id" element={<FoodDetailsPage />} />
<Route path="/map" element={<MapPage />} />
<Route path="/myPage/bookmarkList" element={<BookmarkList />} />
<Route path="/myPage/likeList" element={<LikeList />} />
<Route path="/search" element={<SearchPage />} />
<Route path="/board/main" element={<BoardMain />}/>
<Route path="/board/:id" element={<BoardDetails />}/>
</Routes>
</Router>
);
}
export default App;