Spring 미니 프로젝트와 프론트 연동

API 오류·CORS·실행 순서를 문서화하고 10주 누적 서비스의 계약 검증하기

10주 결과물: API를 실제 화면에서 사용하기

7–9주의 회원 API·JPA·관계를 통합합니다. 정상 응답뿐 아니라 입력 오류·없는 회원·삭제 충돌을 계약대로 반환하고 다른 origin의 작은 프론트에서 조회합니다. 새 프레임워크로 전체를 다시 만드는 과제가 아닙니다.

PBL 참고 VOD의 프론트 예시는 SvelteKit입니다. 이미 배운 프론트 도구를 사용해도 되며 React로 전환하는 것은 필수 조건이 아닙니다. 아래는 React 팀을 위한 선택 예제이며 다른 프론트 도구로 같은 계약을 검증해도 됩니다.

데이터 충돌을 예상한 응답으로 바꾸기

9주의 FK 관계가 있는 회원을 삭제하면 데이터 무결성 오류가 발생합니다. ApiExceptionHandler.java에 import와 메서드를 추가합니다.

Java
import org.springframework.dao.DataIntegrityViolationException;
Java
@ExceptionHandler(DataIntegrityViolationException.class)public ProblemDetail conflict(DataIntegrityViolationException error) {    return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT,        "연결된 데이터 또는 중복 조건 때문에 변경할 수 없습니다.");}

이 handler는 FK 외의 unique 제약 등에도 적용되므로 모든 경우를 “담당 과제 존재”라고 단정하지 않습니다. 구체적인 정책 메시지가 필요하면 Service에서 도메인 예외를 정의하고 DB 제약은 최종 방어선으로 유지합니다. 내부 SQL과 예외 메시지를 사용자에게 그대로 돌려주지 않습니다.

로컬 CORS 범위 설정

브라우저는 origin이 다른 요청을 검사합니다. localhost:4173localhost:8080은 포트가 달라 서로 다른 origin입니다. curl 성공만으로 브라우저 연동 성공을 판단하지 않습니다.

src/main/java/com/example/lion/LocalCorsConfig.java:

Java
package com.example.lion;
import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Profile;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration@Profile("local")public class LocalCorsConfig implements WebMvcConfigurer {    @Override    public void addCorsMappings(CorsRegistry registry) {        registry.addMapping("/api/**")            .allowedOrigins("http://localhost:4173")            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")            .allowedHeaders("Content-Type");    }}
Shell
./gradlew bootRun --args='--spring.profiles.active=local'

CORS는 사용자 인증·권한이 아닙니다. 현재 설정은 로컬 실습 origin만 허용하며 이 문서에는 쿠키 인증을 아직 추가하지 않습니다. 이후 Spring Security를 넣으면 보안 필터의 CORS 설정도 연결하고 인증 방식에 맞춰 다시 검증합니다.

최소 프론트로 확인하기

아래는 React를 사용하는 팀의 선택 연동 예제입니다. PBL VOD의 SvelteKit으로 같은 GET 계약을 확인해도 됩니다. 기존 프로젝트를 덮어쓰지 않고 별도 폴더에서 생성합니다.

Shell
npm create vite@latest lion-client -- --template reactcd lion-clientnpm install

생성된 main.jsx와 HTML 진입점은 유지하고 src/App.jsx를 교체합니다.

JavaScript
import { useState } from 'react';
export default function App() {  const [members, setMembers] = useState([]);  const [message, setMessage] = useState('버튼을 눌러 명단을 확인하세요.');  const [busy, setBusy] = useState(false);
  async function load() {    setBusy(true);    setMembers([]);    setMessage('불러오는 중입니다.');    try {      const response = await fetch('http://localhost:8080/api/members');      if (!response.ok) throw new Error('HTTP ' + response.status);      const data = await response.json();      if (!Array.isArray(data) || !data.every(member =>        member && Number.isSafeInteger(member.id) && member.id > 0 &&        typeof member.name === 'string'      )) throw new Error('응답 형식 오류');      if (new Set(data.map(member => member.id)).size !== data.length) {        throw new Error('중복 ID');      }      setMembers(data);      setMessage(data.length ? data.length + '명' : '등록된 회원이 없습니다.');    } catch {      setMessage('불러오지 못했습니다. 서버와 네트워크를 확인한 뒤 다시 시도해 주세요.');    } finally {      setBusy(false);    }  }
  return <main>    <h1>회원 명단</h1>    <button type="button" disabled={busy} onClick={load}>명단 불러오기</button>    <p role="status">{message}</p>    <ul>{members.map(member => <li key={member.id}>{member.name}</li>)}</ul>  </main>;}

npm run dev -- --port 4173 --strictPort로 실행하고 http://localhost:4173으로 엽니다. 127.0.0.1 주소는 허용한 origin과 다르므로 주소도 일치시킵니다. 포트가 사용 중이면 기존 서버를 확인하고 사용 가능한 포트와 CORS 허용 값을 함께 맞춥니다. 회원은 앞 주 curl POST로 등록한 뒤 버튼을 누릅니다.

이 컴포넌트는 버튼 클릭 시 조회하며 로딩 중 중복 실행을 막습니다. 컴포넌트 전환 중 취소나 검색어마다 요청하는 기능을 추가한다면 React Hooks 문서의 요청 정리·오래된 응답 처리도 적용합니다.

통합 검사와 README

시나리오기대 결과
빈 명단 조회200과 빈 상태 안내
등록 → 조회 → 수정같은 ID의 최신 이름
잘못된 이름400, 저장되지 않음
없는 ID404
정상 삭제204, 이후 GET은 404
연결된 과제가 있는 회원 삭제409, 기존 데이터 보존
백엔드 종료 후 조회오류 안내와 재시도 가능
CORS 허용 origin에서 조회Network 응답과 화면 모두 정상

연결된 과제는 9주 Repository 테스트처럼 테스트 데이터로 생성해 삭제 충돌을 검증합니다. 핵심 업무 규칙은 단위 테스트, 실제 DB 제약은 통합 테스트, 브라우저 연동은 실제 요청으로 검사합니다. H2 테스트 통과를 PostgreSQL 등 운영 DB 호환성 증명으로 대신하지 않습니다.

README에는 JDK·Boot 버전, Wrapper 명령, local profile, 서버와 클라이언트 주소, API 표, 데이터 초기화 정책, 실행 순서, 알려진 한계를 적습니다. 예제 H2 설정은 재시작 시 초기화된다는 사실을 눈에 띄게 적습니다.

세션 진행과 완료 기준

권장 120분: 오류 계약 20분, 프론트 연동 30분, 상호 실행·실패 검사 35분, 수정 15분, 발표 20분입니다.

완료 기준은 팀원이 독립 실행하고 상태 코드와 화면을 함께 확인하며 Controller→Service→Repository 경계를 실제 코드로 설명하는 것입니다. 필수 결과물은 README·요청 결과·관계 테스트·브라우저 시연입니다. 인터넷에 공개하는 경우에는 다음 인증·권한·배포 문서까지 완료해야 합니다.

더 읽어보기

연결된 PBL 미션과 VOD

주차·미션참고 VOD 범위
10주 · 예외·프론트 연동Spring Boot 실습 15·16장

6–10주의 팝오버에는 Spring Boot 실습 강의명·장만 있고 직접 강좌 URL이 없다. 해당 미션의 참고 VOD 안내를 사용한다. 10주 VOD의 프론트 예제는 SvelteKit이며 React 전환을 필수로 요구하지 않는다.