전통적인 Web MVC 방식에서 벗어나, 데이터를 주고받는 표준인 REST API를 설계하고 Spring Boot에서 JSON 형태로 응답을 구현하는 방법을 정리한다.
REST(Representational State Transfer)는 URI를 통해 제어할 자원(Resource)을 명시하고, HTTP Method를 통해 해당 자원을 제어하는 명령을 내리는 방식이다. 대부분의 Open API가 이 방식을 채택하고 있다.
자원(Resource): HTTP URI로 표현한다.
행위(Verb): HTTP Method(GET, POST, PUT, DELETE)로 표현한다.
표현(Representation): 자원을 어떻게 보낼지 결정한다. (주로 JSON, XML 등을 사용)
특징: REST는 딱 정해진 표준 규격은 없으나, URI는 고유한 리소스를 대표해야 하며 행위는 Method로 구분한다는 암묵적인 설계 규칙이 존재한다.
기존의 @Controller는 주로 JSP와 같은 뷰 페이지를 반환하는 데 집중한다. 아래는 전형적인 게시판(GuestBook)의 CRUD 구현 방식이다.
@Controller
public class GuestBookController {
private GuestBookService service;
GuestBookController(GuestBookService service){
this.service = service;
}
// 목록 조회 후 JSP로 이동
@GetMapping({"/listArticle"})
public String listArticle(Model model) {
List<GuestBookDto> list = service.listArticle();
model.addAttribute("articles", list);
return "guestbook/list";
}
// 단건 조회 및 상세 페이지 이동
@GetMapping("/getArticle/{articleno}")
public String getArticle(@PathVariable("articleno") int articleno, Model model) {
GuestBookDto dto = service.getArticle(articleno);
model.addAttribute("dto", dto);
return "guestbook/detail";
}
// 글 삭제 후 리스트로 리다이렉트
@GetMapping("/deleteArticle/{articleno}")
public String deleteArticle(@PathVariable("articleno") int articleno) {
service.deleteArticle(articleno);
return "redirect:/listArticle";
}
}
클라이언트(Vue.js, Mobile App 등)에게 순수하게 데이터(JSON)만 전달하고 싶을 때는 @RestController를 사용한다.
@RestController: 클래스 내의 모든 메소드에 @ResponseBody를 적용한 것과 같은 효과를 준다. 즉, 반환값이 View 이름이 아닌 데이터 자체로 처리된다.
HttpMessageConverter: 자바 객체를 JSON으로 변환하거나, 요청으로 들어온 JSON을 자바 객체로 변환해주는 역할을 수행한다. (@RequestBody, @ResponseBody)
프론트엔드(Vue.js, 5173 포트)에서 백엔드(Spring Boot, 8080 포트)로 데이터를 요청할 때 CORS(Cross-Origin Resource Sharing) 오류가 발생한다. 이는 브라우저의 보안 정책인 SOP(Same-Origin Policy) 때문에 다른 출처의 리소스 접근을 막기 때문이다.
@CrossOrigin("*") 어노테이션을 추가하여 특정 또는 모든 도메인에서의 접근을 허용해야 한다.단순히 객체만 리턴하는 것이 아니라, HTTP 상태 코드(Status Code)를 함께 전달하기 위해 ResponseEntity 객체를 사용한다.
@RestController
@CrossOrigin("*")
@RequestMapping("/admin")
public class AdminController {
private UserService service;
public AdminController(UserService service) {
this.service = service;
}
// 전체 사용자 조회 (ResponseEntity 사용)
@GetMapping("/user")
public ResponseEntity<?> userList() {
List<MemberDto> dtoList = service.userList();
if(dtoList != null && !dtoList.isEmpty()) {
// 데이터가 있을 경우 200 OK와 함께 리스트 반환
return new ResponseEntity<List<MemberDto>>(dtoList, HttpStatus.OK);
} else {
// 결과가 없을 경우 204 No Content 반환
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
}
// 사용자 등록 (POST 방식)
@PostMapping("/user")
public ResponseEntity<?> registerUser(@RequestBody MemberDto dto) {
MemberDto member = service.registerUser(dto);
if (member == null) {
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
} else {
// 성공 시 201 Created 상태 코드 반환
return new ResponseEntity<MemberDto>(member, HttpStatus.CREATED);
}
}
@PutMapping("/user/{id}")
public MemberDto updateUser(@PathVariable String id, @RequestBody MemberDto dto) {
return service.updateUser(dto);
}
}
개발 단계가 아닌 배포 단계에서 Node 서버 없이 Spring(Tomcat) 서버 하나만 운영하고 싶다면, Vue.js 프로젝트의 빌드 결과물을 Spring 프로젝트의 정적 리소스 폴더로 출력하도록 설정한다.
build: {
outDir: 'C:\\path\\to\\spring-project\\src\\main\\resources\\static'
}
위와 같이 설정한 후 npm run build를 실행하면, 생성된 HTML/CSS/JS 파일이 Spring 프로젝트 내부로 들어가게 되어 Spring 서버 하나로 프론트와 백엔드 서비스를 동시에 제공할 수 있다.