[Spring Boot] 11. HTTP와 REST 컨트롤러
- -
11장. HTTP와 REST 컨트롤러
11.1. REST API 의 동작 이해하기
· REST
- HTTP URL로 서버의 자원을 명시하고,
- HTTP 메서드(POST, GET, PATCH, DELETE)로 해당 자원에 대해 CRUD(생성, 조회, 수정, 삭제)하는 것.
· API
- 클라이언트가 서버의 자원을 요청할 수 있도록 서버에서 제공하는 인터페이스(interface)
클라이언트가 보내는 HTTP 요청 메시지의 첫 줄에는 시작 라인인 요청 라인(request line)이 있고, 그 아래에는 헤더(header)와 본문(body)가 있다.
응답 메시지의 첫 줄에도 시작 라인인 상태 라인(status line)이 있고, 그 아래 헤더(header)와 본문(body)가 있다. 응답으로 오는 상태 코드는 요청이 성공했을 떄는 200, 요청한 정보를 찾을 수 없을 때는 404, 서버에 오류가 났을 때는 500을 반환한다.
REST API의 응답 표준으로 사용하는 JSON은 키와 값의 쌍으로 된 속성으로 데이터를 표현한다. 아래의 코드에서 볼 수 있듯이 JSON의 값으로 또 다른 JSON 데이터나 배열을 넣을 수도 있다.
{
"id" : 1,
"name" : "Park",
address : {
"street" : "Nambu Street 151",
"suite" : "Central Villat 301"
},
"likes" : ["singing", "jogging", "writing"]
}
11.2. REST API의 구현 과정
· 조회 요청 : /api/articles 또는 /api/articles/{id}
- GET 메서드로 Articles 전체 목록 또는 단일 Article을 조회한다.
· 생성 요청 : /api/articles
- POST 메서드로 새로운 Article을 생성해 목록에 저장한다.
· 수정 요청 : /api/articles/{id}
- PATCH 메서드로 특정 Article의 내용을 수정한다.
· 삭제 요청 : api/articles/{id}
- DELETE 메서드로 특정 Article을 삭제한다.
주소의 설계가 끝난 후에는 URL 요청을 받아 그 결과를 JSON으로 반환해줄 컨트롤러도 만들어야 한다. 게시판을 만들 때는 일반 컨트롤러(ArticleController)를 사용했지만, REST API로 요청과 응답을 주고받을 때는 REST 컨트롤러를 사용한다. 또한 응답할 때 적절한 상태 코드를 반환하기 위해 ResponseEntity 클래스를 활용한다.
11.3. REST API 구현하기
src > main > java > com.example.firstproject > api > firstApiController
@RestController // REST API 전용 컨트롤러
public class FirstApiController {
@GetMapping("/api/hello") // URL 요청 접수
public String hello() { // hello world! 문자열 반환
return "hello world!";
}
}
Talend API Tester에서 확인해보면 응답으로 200이 돌아온다. 응답의 BODY 부분을 보면 hello world!가 반환된 것을 확인할 수 있다.
· REST 컨트롤러와 일반 컨트롤러 차이
앞서 만든 FirstController(일반 컨트롤러)는 "/hi"라는 URL 요청을 보냈을 때 greetings.mustache 파일을 반환하도록 되어 있다. 실제로 실행했을 때 아래의 그림처럼 나온다.
즉, Talend API Tester에서 확인해보면 REST 컨트롤러(FirstApiController)는 JSON이나 텍스트 같은 데이터를 반환하는 반면 일반 컨트롤러(FirstController)는 뷰 페이지를 반환하는 것을 알 수 있다.
11.3.2. REST API : GET 구현하기
· 전체 게시글 조회
src > main > java > com.example.firstproject > api > ArticleApiController
@RestController // REST 컨트롤러 선언
public class ArticleApiController {
@Autowired // 게시글 리파지터리 의존성 주입
private ArticleRepository articleRepository;
// GET : 모든 게시글 조회
@GetMapping("/api/articles") // URL 요청 접수
public List<Article> index() {
return articleRepository.findAll();
}
}
@GetMapping()으로 "/api/articles" 주소로 오는 URL 요청을 받고, 전체 게시글을 조회해야 하므로 메서드 수행 결과로 Article 묶음을 반환해야 한다. 따라서 List<Article>을 반환하는 index() 메서드를 정의한다. return 문에는 articleRepository의 findAll() 메서드를 통해 DB에 저장된 모든 Article을 가져와 반환한다. 이때 findAll() 메서드를 위해 articleRepository를 선언하고 @Autowired 어노테이션을 통해 의존성을 주입한다.
· 전체 게시글 조회
src > main > java > com.example.firstproject > api > ArticleApiController
// GET : 단일 게시글 조회
@GetMapping("/api/articles/{id}")
public Article show(@PathVariable Long id) { // URL의 id를 매개변수로 받아오기
return articleRepository.findById(id).orElse(null);
}
@GetMapping()으로 "/api/articles/{id}" 주소로 오는 URL 요청을 받고, 단일 게시글을 조회해야 하므로 메서드 수행 결과로 Article을 반환하는 show() 메서드를 정의한다. return 문은 DB에서 id로 검색해 얻은 엔티티를 가져오고, 해당 엔티티가 없는 경우 null을 반환하도록 한다. 요청 URL의 id를 매개변수로 받기 위해 @PathVariable을 붙인다.
11.3.3. REST API : POST 구현하기
src > main > java > com.example.firstproject > api > ArticleApiController
// POST
@PostMapping("api/articles")
public Article create(@RequestBody ArticleForm dto) {
Article article = dto.toEntity();
return articleRepository.save(article);
}
@PostMapping()으로 "/api/articles" 주소로 오는 URL 요청을 받고, 반환형이 Article인 create 메서드를 정의한다. 폼 데이터는 dto 객체에 담기므로 dto를 매개변수로 받아와야 한다. 이렇게 받아온 dto는 DB에서 활용할 수 있도록 엔티티로 변환하고 articleRepository를 통해 DB에 저장된 후에 반환된다.
하지만 REST API에서 데이터를 생성할 때는 JSON 데이터를 받아와야 하므로 단순히 매개변수로 dto를 쓴다고 해서 받아 올 수 있는게 아니다. 따라서 dto 매개변수 앞에 @RequestBody 어노테이션을 추가해야 한다.
11.3.4. REST API : PATCH 구현하기
· 데이터 전체 수정
src > main > java > com.example.firstproject > api > ArticleApiController
@Slf4j // 로그를 찍기 위한 어노테이션
@RestController // REST 컨트롤러 선언
public class ArticleApiController {
// 중략
// PATCH
@PatchMapping("/api/articles{id}")
public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) {
// 1. DTO를 엔티리로 변환하기
Article article = dto.toEntity();
log.info("id : {}, article : {}", id, article.toString());
// 2. 타겟 조회하기
Article target = articleRepository.findById(id).orElse(null);
// 3. 잘못된 요청 처리하기
if (target == null || id != article.getId()) {
// 400, 잘못된 요청 응답!
log.info("잘못된 요청! id : {}, article : {}", id, article.toString());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
// 4. 업데이트 및 정상(200) 응답하기
Article updated = articleRepository.save(article); // article 엔티티 DB에 저장
return ResponseEntity.status(HttpStatus.OK).body(updated); // 정상 응답
}
}
먼저 수정용 엔티티를 만들어야 한다. 클라이언트에서 받은 수정 폼 데이터가 담긴 dto를 DB에서 활용할 수 있도록 엔티티로 변환하고 클래스 위에 @Slf4j 어노테이션을 추가해 로그를 찍어본다.
두번째로, DB에서 대상 엔티티를 조회해 가져와야 한다. articleRepository의 findById() 메서드를 통해 DB에서 해당 id를 가진 엔티티를 가져오되 없다면 null을 반환하고, target 변수에 저장한다.
다음으로 잘못된 요청이 들어올 경우를 고려해야 한다. 게시글이 3번까지 있는데 100번 데이터의 수정 요청이 들어오거나, 수정 요청은 1번인데 요청 본문의 id가 3번이라면 요청 자체가 잘못된 것을 처리해야 한다. 클라이언트 요청 오류는 상태 코드 400을 반환해야 하므로update() 메서드의 반환형이 Article이 아닌 ResponseEntity에 담아서 반환하는 ResponseEntity<Article>으로 수정해야 한다.
마지막으로 정상 응답을 처리해야 한다. article 엔티티에 담긴 수정 데이터를 DB에 저장한 후 updated라는 변수에 저장하고 수정된 데이터는 ResponseEntity에 담아 보낸다. 이때 상태는 정상 응답이므로 200 또는 HttpStatus.OK를 싣고, 본문(Body)에는 반환할 데이터인 updated를 싣는다.
이름 | 설명 |
ResponseEntity | REST 컨트롤러의 반환형 즉, REST API 응답을 우해 사용하는 클래스 HTTP 상태 코드, 헤더, 본문을 실어 보낸다. |
HttpStatus | HTTP 상태 코드를 관리하는 클래스 |
잘못된 요청을 보내기 위해 URL의 id는 1번, 본문의 id는 3번으로 보내면 400이라는 클라리언트 에러 응답이 오는 것을 볼 수 있다. 인텔리제이의 실행창 로그를 보면 id는 1번이고 본문의 JSON 데이터는 3번이라서 잘못된 요청인 것을 알려준다.
이번에는 100번 게시글의 수정 요청을 보내보자. URL과 본문의 id를 모두 100으로 수정하면 역시 클라이언트 에러 응답이 400으로 돌아온다.
· 데이터 일부만 수정
src > main > java > com.example.firstproject > api > ArticleApiController
// 4. 업데이트 및 정상(200) 응답하기
target.patch(article); // 기존 데이터에 새 데이터 붙이기
Article updated = articleRepository.save(target); // article 엔티티 DB에 저장
return ResponseEntity.status(HttpStatus.OK).body(updated); // 정상 응답
src > main > java > com.example.firstproject > entity > Article
public void patch(Article article) {
if (article.title != null)
this.title = article.title;
if (article.content != null)
this.content = article.content;
}
target에는 기존 데이터가 article에는 수정할 데이터가 있으므로 기존 데이터에 새 데이터를 붙여주면 일부 데이터만 수정이 가능하다. 따라서 patch() 메서드가 있다고 가정한 후에 Article 클래스에 메서드를 생성해준다.
11.3.5. REST API : DELETE 구현하기
src > main > java > com.example.firstproject > api > ArticleApiController
// 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).body(null);
}
// 3. 대상 삭제하기 + 정상 응답(200) 반환하기
articleRepository.delete(target);
return ResponseEntity.status(HttpStatus.OK).body(null);
}
// body(null) 대신 build() 작성 가능
return ResponseEntity.status(HttpStatus.OK).body(null);
'BackEnd > 스프링부트3 백엔드 개발 입문' 카테고리의 다른 글
[Spring Boot] 13. 테스트 코드 작성하기 (0) | 2024.01.24 |
---|---|
[Spring Boot] 12. 서비스 계층과 트랜잭션 (0) | 2024.01.23 |
[Spring Boot] 10. REST API와 JSON (0) | 2024.01.20 |
[Spring Boot] 09. CRUD와 SQL 쿼리 (0) | 2024.01.18 |
[Spring Boot] 08. 게시글 삭제하기 (0) | 2024.01.18 |
소중한 공감 감사합니다