Spring WebFlux를 이용한 chat 프로그램 - Dynamic Route 설정
2025. 5. 3. 10:56ㆍJAVA/Spring Boot
기본 Vite에서 라우팅 하는 방법은 다음과 같다.
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Signup from './pages/Signup.jsx';
import Login from './pages/Login';
import "./App.css"
function App() {
return (
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
</Routes>
</Router>
);
}
모든 페이지를 Import 하고 일일이 Route를 입력해 주는 방식이다.
근데 처음 Vite를 접한 나에게는 이게 너무 비효율 적으로 보인다.
그래서 ChatGPT에 문의를 했서 다음과 같이 처리 했다.
아~!!! 그전에 먼저 프론트엔드 구조는 다음과 같다.
위의 구조를 가지고 시작한다.
DB Table 생성
CREATE TABLE routes (
id SERIAL PRIMARY KEY,
path VARCHAR(255) NOT NULL,
component_name VARCHAR(255) NOT NULL,
UNIQUE (path, component_name)
);
Data Insert
INSERT INTO routes (path, component_name) VALUES ('/login', 'auth/Login');
INSERT INTO routes (path, component_name) VALUES ('/signup', 'auth/Signup');
백엔드 작업
Route.java
package com.company.common.database.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import lombok.Data;
/**
* routes 테이블과 매핑되는 라우터 엔티티
*/
@Table("routes")
@Data
public class Route {
@Id
private Long id; // 내부적으로 PK 역할 (Auto Increment)
private String path;
private String componentName;
}
RouteService.java
package com.company.route.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.company.common.database.entity.Route;
import com.company.route.service.RouteService;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api/v1/routes")
@RequiredArgsConstructor
public class RouteController {
private final RouteService routeService;
/**
* 모든 라우트 목록 조회 API
*/
@GetMapping
public Flux<Route> getAllRoutes() {
return routeService.findAllRoutes();
}
}
RouteRepository.java
package com.company.route.repository;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import com.company.common.database.entity.Route;
/**
* 화면(Route)용 PostgreSQL R2DBC Repository
*/
public interface RouteRepository extends ReactiveCrudRepository<Route, Long> {
}
RouteController.java
package com.company.route.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.company.common.database.entity.Route;
import com.company.route.service.RouteService;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api/v1/routes")
@RequiredArgsConstructor
public class RouteController {
private final RouteService routeService;
/**
* 모든 라우트 목록 조회 API
*/
@GetMapping
public Flux<Route> getAllRoutes() {
return routeService.findAllRoutes();
}
}
DB에서 Route 정보를 모두 조회해서 반환해 주는게 끝이다.
프론트엔드 작업
App.jsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { routeService } from './api/service/route/RouteService';
import "./App.css"
/**
* 1. Vite의 import.meta.glob를 사용 사용하여 page 폴더 하위의 모든 .jsx 파일을 스캔한다.
*/
const pages = import.meta.glob("./pages/**/*.jsx");
/**
* 컴포넌트 이름에 맞는 컴포넌트를 동적으로 로딩
* @param {string} componentName - ex) "ChatRoom", "Signup"
*/
async function loadComponent(componentName) {
// 경로 기반 정확한 파일 매칭
const targetPath = `./pages/${componentName}.jsx`;
// 파일명에 컴포넌트 이름이 포함된 파일을 찾는다
if (pages[targetPath]) {
const module = await pages[targetPath](); // 동적 import
return module.default;
}
console.error(`컴포넌트를 찾을 수 없습니다. [${targetPath}]`)
throw new Error(`컴포넌트를 찾을 수 없습니다: ${componentName}`);
}
function App() {
const [routeList, setRouteList] = useState([]);
const [components, setComponents] = useState({});
/*
* 2. DB에서 Route 정보를 조회해서 Component와 RouteList를 생성한다.
* DB에서 조회한 Route의 componentName과 일치하는 항목이 있는지 조회해서 loadComponent 함수를 통해 Components에 추가한다.
* 또한. Route 목록도 저장한다.
*/
useEffect( () => {
const fetchRoutes = async () => {
try {
// Database에서 route 목록 조회
const response = await routeService.select();
const routes = response.data;
const comps = {};
for (const route of routes) {
try {
//componentName과 일치하는 Component 항목을 찾는다.
const comp = await loadComponent(route.componentName);
comps[route.componentName] = comp;
} catch (err) {
console.error(err);
}
}
setComponents(comps); // Components 생성
setRouteList(routes); // Route 목록 생성
} catch (error) {
console.error("라우트 불러오기 실패:", error);
}
};
fetchRoutes();
}, []);
return (
<Router>
<Routes>
{/* 3. Route 목록을 Loof 돌리면서 route의 componentName에 해당하는 component를 조회한다. */}
{routeList.length === 0 ? (
<Route path="*" element={<div>라우트 불러오는 중...</div>} />
) : (
routeList.map((route, index) => {
const Component = components[route.componentName];
if (!Component) return null;
return (
<Route
key={index}
path={route.path}
element={<Component />}
/>
);
})
)}
</Routes>
</Router>
);
}
export default App;
설명은 주석에 최대한 달았으니 참고 하면 된다.
RouteService.jsx
import axiosInstance from "../../instance/axiosInterceptor";
/**
* Route 처리 Service
*/
class RouteService {
/**
* 모든 라우트 목록 조회
* @returns
*/
async select() {
return await axiosInstance.get('/routes')
}
}
export const routeService = new RouteService();
위와 같이 Route 정보를 DB에서 동적으로 관리하도록 처리 완료했다.
추후 권한이 필요한 경우 여기서 확장하여 처리하면 될 듯하다.
'JAVA > Spring Boot' 카테고리의 다른 글
Spring WebFlux를 이용한 chat 프로그램 - 채팅방생성(1) (0) | 2025.05.08 |
---|---|
Spring WebFlux를 이용한 chat 프로그램 - WebSocket 설정 (0) | 2025.05.08 |
Spring WebFlux를 이용한 chat 프로그램 - 회원가입 및 로그인 (0) | 2025.05.02 |
Spring WebFlux를 이용한 chat 프로그램 - 백엔드 기본 구성 (0) | 2025.05.01 |
Spring WebFlux를 이용한 chat 프로그램 - 프론트엔드 환경 구성 (0) | 2025.05.01 |