스프링 부트3 제 11장
REST API, 어떻게 만들죠?
REST API의 구현 과정
URL을 설계하고, 그에 따른 요청을 받아 JSON으로 반환해 줄 컨트롤러를 만들면 된다.
- 조회 요청:
/api/articles또는/api/articles/{id}-> GET 메서드로 모든 게시글 또는 단일 게시글 조회 - 생성 요청:
/api/articles/-> POST 메서드로 새로운 게시글 저장 - 수정 요청:
/api/articles/{id}-> PATCH 메서드로 특정 게시글 수정 - 삭제 요청:
/api/articles/{id}-> DELETE 메서드로 특정 게시글 삭제
REST 컨트롤러와 일반 컨트롤러의 차이
일반 컨트롤러
1
2
3
4
5
6
7
8
@Controller // 이 클래스가 컨트롤러임을 선언하는 어노테이션
public class FirstController {
@GetMapping("/hi") // URL 매핑
public String niceToMeetYou(Model model) {
model.addAttribute("username", "neko");
return "greetings"; // greetings.mustache 뷰 반환
}
}
REST 컨트롤러
1
2
3
4
5
6
7
@RestController // REST API용 컨트롤러
public class FirstApiController {
@GetMapping("/api/hello")
public String hello() {
return "hello world"; // 일반 텍스트 반환
}
}
REST 컨트롤러는 @RestController 어노테이션을 붙여 선언한다.
일반 컨트롤러가 뷰 페이지를 반환한다면, REST 컨트롤러는 JSON이나 텍스트 같은 데이터를 반환한다.
REST 컨트롤러 구현 코드(RestApiController)
초안
1
2
3
4
5
6
7
8
9
10
package com.example.firstproject.api;
// ... import 생략
@Slf4j // 로그
@RestController // REST API용 컨트롤러
public class ArticleApiController {
@Autowired // 의존성 주입
private ArticleRepository articleRepository;
// ... 각 요청 처리
}
GET 요청 처리
1
2
3
4
5
6
7
8
9
10
// GET
@GetMapping("/api/articles")
public List<Article> index() { // 모든 게시글 보기
return articleRepository.findAll();
}
@GetMapping("/api/articles/{id}")
public Article show(@PathVariable Long id) { // 단일 게시글 보기
return articleRepository.findById(id).orElse(null);
}
PathVariable은 URL의 변수를 가져오기 위한 것이다. (자꾸 까먹는다…)
테스트를 해보면 게시글을 JSON 데이터로 가져오는 것을 알 수 있다. Talend API Tester를 활용해서 확인해보면 된다.
반환값이 JSON이 아니라도 REST API가 자동으로 JSON으로 변환해준다고 한다. 자세한 설명은 여기
This application uses the Jackson JSON library to automatically marshal instances of type Greeting into JSON. Jackson is included by default by the web starter.
POST 요청 처리
1
2
3
4
5
6
7
// POST
@PostMapping("/api/articles")
public Article create(@RequestBody ArticleForm dto) { // 게시글 쓰기
// RequestBody는 본문에 실어 보내는 데이터를 메서드의 매개변수로 받아올 수 있게 하는 어노테이션
Article article = dto.toEntity();
return articleRepository.save(article);
}
REST API에서 데이터를 생성할 때는 JSON 데이터를 받아와야 하므로, DTO만으로 매개변수를 받아올 수 없다. 따라서 @RequestBody 어노테이션을 추가해주어 body에 실어 보내는 데이터를 매개변수로 받아올 수 있다.
즉, 정리하자면 클라이언트가 서버로 JSON 데이터 형식 게시글을 보내면, @RequestBody가 DTO 형식에 맞게 변환해준다는 뜻이다.
PATCH 요청 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// PATCH
@PatchMapping("/api/articles/{id}")
public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) { // 일부 데이터만 수정할 경우
// 1. 수정용 엔티티 생성
Article article = dto.toEntity();
log.info("id: {}, article: {}", id, article.toString());
// 2. DB에 대상 엔티티가 있는지 확인
Article target = articleRepository.findById(id).orElse(null);
// 3. 잘못된 요청 처리
if (target == null || id != article.getId()) { // DB에 데이터가 없거나 URL의 id와 게시글의 id가 맞지 않는 경우
log.info("Error! id: {}, article: {}", id, article.toString());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); // ResponseEntity는 REST 컨트롤러의 응답을 위해 사용되는 클래스
}
// 4. 있으면 업데이트하고 정상 응답 보내기
target.patch(article); // Article entity에 메서드 추가
Article updated = articleRepository.save(target);
return ResponseEntity.status(HttpStatus.OK).body(updated);
}
ResponseEntity에 결과를 실어 응답을 보낼 수 있다.
잘못된 요청일 경우 400(HttpStatus.BAD_REQUEST)와 본문에 null을 실어 보낸다.
정상적인 요청일 경우 200(HttpStatus.OK)와 본문에 업데이트 된 게시글을 실어 보낸다.
ResponseEntity는 REST 컨트롤러의 반환형, 즉 REST API의 응답을 위해 사용되는 클래스.
HTTP의 상태 코드, 헤더, 본문을 실어 보낼 수 있음.
공식 문서는 여기. 더 자세한 설명은 여기
Article entity의 patch() 메서드
1
2
3
4
5
6
7
8
9
10
// ... 생략
public void patch(Article article) { // 글 수정
if (article.title != null) {
this.title = article.title;
}
if (article.content != null) {
this.content = article.content;
}
}
DELETE 요청 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// DELETE
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Article> delete(@PathVariable Long id) { // 게시글 삭제
// 1. 대상 찾기
Article target = articleRepository.findById(id).orElse(null);
// 2. 잘못된 요청 처리
if (target == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); // body(null)
}
// 3. 대상 삭제
articleRepository.delete(target);
return ResponseEntity.status(HttpStatus.OK).build();
}
build()는 body(null)과 같은 결과를 낸다.