웹 서비스를 사용하는 클라이언트에는 웹 브라우저만 있는 게 아니다. 스마트폰, 스마트워치, 태블릿, CCTV, 각종 센서 등이 모두 클라이언트이다. IT 기기의 발전에 따라 지금도 수많은 클라이언트가 만들어지고 있다.
서버는 이러한 모든 클라이언트의 요청에 응답해야 한다. 웹 브라우저뿐만 아니라 어떤 기기가 와도 기기에 맞는 뷰 페이지를 응답해야 한다.
그런데 이런 기기들은 앞으로 끝없이 나올텐데 그때마다 서버가 일일이 대응하기란 쉽지 않다. 그래서 이를 위해 REST API를 사용한다.
REST API(Representational State Transfer API)는 서버의 자원을 클라이언트에 구애받지 않고 사용할 수 있게 하는 설계 방식이다. REST API 방식에서는 HTTP 요청에 대한 응답으로 서버의 자원을 반환한다. 서버에서 보내는 응답이 특정 기기에 종속되지 않도록 모든 기기에서 통용될 수 있는 데이터를 반환한다.
REST API의 동작에서 서버는 클라이언트의 요청에 대한 응답으로 화면(view)가 아닌 데이터(data)를 전송한다. 이때 사용하는 응답 데이터가 JSON(JavaScript Object Notation)이다. 과거에는 응답데이터로 XML을 많이 사용했지만, 최근에는 JSON으로 통일되는 추세이다.
XML과 JSON의 데이터 형식을 간단히 비교하면 다음과 같다.
// XML : Extensible Markup Language
<article-form>
<id>1</id>
<title>가가가가</title>
<content>1111</content>
</article-form>
// JSON : JavaScript Object Notation
{
"id" : 1,
"title" : "가가가가",
"content" : "1111"
}
JSON 데이터는 키(key)와 값(value)으로 구성된 정렬되지 않은 속성(property)의 집합이다. 키는 문자열이므로 항상 큰따옴표("")로 감싸고, 값은 문자열인 경우에만 큰 따옴표("")로 감싼다.
API(Application Programming Interface)란 애플리케이션을 간편히 사용할 수 있게 하는, 미리 정해진 일종의 약속으로, 사용자와 프로그램 간 상호 작용을 돕는다. 자판기를 예로 들면, 자판기 버튼이 하나의 API가 된다. 콜라 버튼을 누르면 콜라가, 사이다 버튼을 누르면 사이다가 나오는 것은 각 버튼에 따라 반환될 음료를 미리 약속했기 때문이다. 이와 마찬가지로 '윈도우 API'는 윈도우 개발을 위해 미리 정해진 약속, '자바 API'는 자바 개발을 위해 미리 만들어진 약속이라고 보면 된다.
Talend API Tester 프로그램을 이용하면, HTTP 요청을 보내고 돌아온 응답을 쉽게 확인할 수 있다. 이는 크롬 웹 스토어에서 설치가 가능하다.

HTTP 상태 코드는 클라이언트가 보낸 요청이 성공했는지, 실패했는지 알려 주는 코드이다. 코드는 100번대부터 500번대까지 5개 그룹으로 나뉘어 있다.
Talend API Tester 맨 아래에서 HTTP 메시지를 확인할 수 있다. HTTP 메시지는 시작 라인(start line), 헤더(header), 빈 라인(blank line), 본문(body)으로 구성된다.

우선 제대로 해보기 전에 맛보기부터 하겠다.
패키지 com.example.firstproject.api를 생성하고 그 안에 FirstApiController 클래스 생성하고 코드를 작성한다.
package com.example.firstproject.api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // REST API용 컨틀롤러
public class FirstApiController {
@GetMapping("/api/hello") // URL 요청 접수
public String hello() { // hello world! 문자열 반환
return "hello world!";
}
}
이렇게 해서 Api Tester로 실행해보면

이렇게 데이터만 나온다.
만약 REST API로 하지 않은 경우에는

이런식으로 뷰 페이지가 반환이 된다.
이번에는 패키지 com.example.firstproject.api 내에 ArticleApiController를 만든다.
@Slf4j
@RestController // REST 컨트롤러 선언
public class ArticleApiController {
@Autowired // 게시글 리파지터리 주입
private ArticleRepository articleRepository;
// GET
@GetMapping("/api/articles") // URL 요청 접수
public List<Article> index() { // index() 메서드 정의
return articleRepository.findAll();
}
@GetMapping("/api/articles/{id}")
public Article show(@PathVariable Long id) { // URL의 id를 매개변수로 받아오기
return articleRepository.findById(id).orElse(null);
}
}
이제 GET 요청이 잘 되는지 확인해보면

전체 게시글 반환도 잘 되고

단일 게시글 반환도 됨을 확인할 수 있다.
@PostMapping("/api/articles") // URL 요청 접수
public Article create(@RequestBody ArticleForm dto) { // create() 메서드 정의
Article article = dto.toEntity();
return articleRepository.save(article);
}
코드 작성 후, 서버를 재시작하고 POST 요청을 보내보면 성공 응답이 돌아오며, GET요청으로 추가가 되었음을 확인할 수 있다.


// PATCH
@PatchMapping("/api/articles/{id}") // URL 요청 접수
public ResponseEntity<Article> update(@PathVariable Long id, // update() 메서드 정의
@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); // ResponseEntity 반환
}
// 4. 업데이트 및 정상 응답(200)
target.patch(article);
Article updated = articleRepository.save(target);
return ResponseEntity.status(HttpStatus.OK).body(updated); // 정상 응답
}
Article 엔티티 코드에 patch()메서드를 추가한다.
public void patch(Article article) {
if(article.title != null)
this.title = article.title;
if(article.content != null)
this.content = article.content;
}
patch() 메서드는 수정할 내용이 있는 경우에만 동작하면 된다. 따라서 if 문으로 article(수정 엔티티)의 title이 null이 아니면(article.title != null), 즉 갱신할 값이 있다면 this(target)의 title을 갱신해 준다. 같은 방법으로 content도 갱신해 준다.
이제 PATCH 요청으로 수정이 잘 되는지 확인한다.


부분 수정을 해도 잘 되는 것을 확인했다.
@DeleteMapping("/api/articles/{id}") // URL 요청 접수
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. 대상 삭제하기
articleRepository.delete(target);
return ResponseEntity.status(HttpStatus.OK).build();
}


이제 DELETE 요청을 보내서 삭제가 잘 되는지 확인해본다.