RESTful API 생성
Ktor를 사용하여 Kotlin에서 RESTful API를 생성하는 방법
코드 예제: tutorial-server-restful-api
사용된 플러그인:
이 튜토리얼에서는 JSON 파일을 생성하는 RESTful API 예제를 통해 Kotlin과 Ktor를 사용하여 백엔드 서비스를 구축하는 방법을 설명합니다.
다음 내용을 배우게 됩니다:
- JSON 직렬화(serialization)를 사용하는 RESTful 서비스 생성하기.
- Content Negotiation (콘텐츠 협상)프로세스 이해하기.ContentNegotiation 플러그인은 클라이언트와 서버 간의 미디어 유형 협상과 특정 형식의 콘텐츠 직렬화/역직렬화라는 두 가지 주요 목적을 수행합니다.
- Ktor 내에서 REST API를 위한 라우트 정의하기.
사전 준비 사항
이 튜토리얼은 독립적으로 진행할 수 있지만,
IntelliJ IDEA를 설치할 것을 권장하지만, 원하는 다른 IDE를 사용할 수도 있습니다.
Hello RESTful Task Manager
이 튜토리얼에서는 기존의 Task Manager(작업 관리자)를 RESTful 서비스로 다시 작성해 보겠습니다. 이를 위해 여러 Ktor
기존 프로젝트에 수동으로 추가할 수도 있지만, 새 프로젝트를 생성한 다음 이전 튜토리얼의 코드를 점진적으로 추가하는 것이 더 간단합니다. 진행하면서 모든 코드를 다시 반복할 것이므로 이전 프로젝트를 가지고 있을 필요는 없습니다.
Ktor Project Generator로 이동합니다.
Project artifact 필드에 프로젝트 아티팩트 이름으로 com.example.ktor-rest-task-app를 입력합니다.

Plugins 섹션에서 Add 버튼을 클릭하여 다음 플러그인들을 검색하고 추가합니다:
- Content Negotiation
- kotlinx.serialization
- Static Content
플러그인을 추가하면 프로젝트 설정 아래에 모든 플러그인이 나열되는 것을 볼 수 있습니다. 
Download 버튼을 클릭하여 Ktor 프로젝트를 생성하고 다운로드합니다.
IntelliJ IDEA에서 Ktor 프로젝트 열기, 탐색 및 실행 튜토리얼에서 설명한 대로 IntelliJ IDEA에서 프로젝트를 엽니다.
src/main/kotlin으로 이동하여 model이라는 하위 패키지를 생성합니다.
model 패키지 안에 새 Task.kt 파일을 생성합니다.
Task.kt 파일을 열고 우선순위를 나타내는
enum과 작업을 나타내는class를 추가합니다:kotlin이전 튜토리얼에서는 확장 함수를 사용하여
Task를 HTML로 변환했습니다. 이번에는Task클래스에kotlinx.serialization라이브러리의Serializable타입 어노테이션이 추가되었습니다.Routing.kt 파일을 열고 기존 코드를 아래 구현으로 교체합니다:
kotlin이전 튜토리얼과 마찬가지로 URL
/tasks에 대한 GET 요청 라우트를 만들었습니다. 이번에는 작업 목록을 수동으로 변환하는 대신 목록 자체를 반환하고 있습니다.IntelliJ IDEA에서 실행 버튼(
)을 클릭하여 애플리케이션을 시작합니다.
브라우저에서 http://0.0.0.0:8080/tasks로 이동합니다. 아래와 같이 JSON 형식의 작업 목록을 볼 수 있습니다:

상당히 많은 작업이 우리 대신 수행되고 있습니다. 정확히 무슨 일이 일어나고 있는 걸까요?
Content Negotiation(콘텐츠 협상) 이해하기
브라우저를 통한 Content Negotiation
프로젝트를 생성할 때
HTTP에서 클라이언트는 Accept 헤더를 통해 자신이 렌더링할 수 있는 콘텐츠 유형을 알립니다. 이 헤더의 값은 하나 이상의 콘텐츠 유형입니다. 위의 경우 브라우저에 내장된 개발자 도구를 사용하여 이 헤더의 값을 확인할 수 있습니다.
다음 예시를 살펴보세요:
*/*가 포함되어 있음에 유의하세요. 이 헤더는 HTML, XML 또는 이미지를 허용하지만, 그 외의 다른 모든 콘텐츠 유형도 허용하겠다는 신호입니다.
Content Negotiation 플러그인은 브라우저에 데이터를 다시 보낼 형식을 찾아야 합니다. 프로젝트의 생성된 코드 내부를 보면 src/main/kotlin 안에 Serialization.kt라는 파일이 있으며, 여기에는 다음 내용이 포함되어 있습니다:
이 코드는 ContentNegotiation 플러그인을 설치하고 kotlinx.serialization 플러그인을 구성합니다. 이렇게 하면 클라이언트가 요청을 보낼 때 서버가 JSON으로 직렬화된 객체를 다시 보낼 수 있습니다.
브라우저의 요청의 경우, ContentNegotiation 플러그인은 JSON만 반환할 수 있다는 것을 알고 있고, 브라우저는 수신된 모든 것을 표시하려고 시도합니다. 따라서 요청이 성공합니다.
이를 시뮬레이션하기 위해 src/main/resources/static 내부의 index.html 페이지를 열고 기본 콘텐츠를 다음으로 교체합니다:
html이 페이지는 HTML 폼과 빈 테이블을 포함하고 있습니다. 폼을 제출하면 JavaScript 이벤트 핸들러가
Accept헤더를application/json으로 설정하여/tasks엔드포인트로 요청을 보냅니다. 반환된 데이터는 역직렬화되어 HTML 테이블에 추가됩니다.IntelliJ IDEA에서 재실행 버튼(
)을 클릭하여 애플리케이션을 다시 시작합니다.
URL http://0.0.0.0:8080/static/index.html로 이동합니다. View The Tasks 버튼을 클릭하여 데이터를 가져올 수 있습니다:

프로덕션 환경에서는 일반적으로 JSON을 브라우저에 직접 표시하지 않습니다. 대신 브라우저에서 실행되는 JavaScript 코드가 요청을 수행한 다음 반환된 데이터를 SPA(Single Page Application)의 일부로 표시합니다. 일반적으로 이러한 종류의 애플리케이션은 React, Angular 또는 Vue.js와 같은 프레임워크를 사용하여 작성됩니다.
GET 라우트 추가하기
이제 콘텐츠 협상 프로세스에 익숙해졌으니,
작업 레포지토리 재사용
작업 레포지토리는 수정 없이 재사용할 수 있으므로, 이를 먼저 수행하겠습니다.
model 패키지 안에 새 TaskRepository.kt 파일을 생성합니다.
TaskRepository.kt를 열고 아래 코드를 추가합니다:
kotlin
GET 요청 라우트 재사용
레포지토리를 만들었으므로 GET 요청을 위한 라우트를 구현할 수 있습니다. 작업을 HTML로 변환하는 것을 더 이상 걱정할 필요가 없으므로 이전 코드를 단순화할 수 있습니다:
src/main/kotlin에 있는 Routing.kt 파일로 이동합니다.
Application.configureRouting()함수 내부의/tasks라우트 코드를 다음 구현으로 업데이트합니다:kotlin이제 서버는 다음과 같은 GET 요청에 응답할 수 있습니다:
/tasks는 레포지토리의 모든 작업을 반환합니다./tasks/byName/{taskName}은 지정된taskName으로 필터링된 작업을 반환합니다./tasks/byPriority/{priority}는 지정된priority로 필터링된 작업들을 반환합니다.
IntelliJ IDEA에서 재실행 버튼(
)을 클릭하여 애플리케이션을 다시 시작합니다.
기능 테스트하기
브라우저에서 이러한 라우트를 테스트할 수 있습니다. 예를 들어, http://0.0.0.0:8080/tasks/byPriority/Medium으로 이동하면 Medium 우선순위를 가진 모든 작업이 JSON 형식으로 표시되는 것을 볼 수 있습니다:

이러한 요청은 일반적으로 JavaScript에서 오기 때문에 더 세밀한 테스트가 바람직합니다. 이를 위해 Postman과 같은 전문 도구를 사용할 수 있습니다.
Postman에서 URL이
http://0.0.0.0:8080/tasks/byPriority/Medium인 새 GET 요청을 만듭니다.Headers 패널에서 Accept 헤더의 값을
application/json으로 설정합니다.Send를 클릭하여 요청을 보내고 응답 뷰어에서 응답을 확인합니다.

프로젝트 루트 디렉토리에 새 REST Task Manager.http 파일을 생성합니다.
REST Task Manager.http 파일을 열고 다음 GET 요청을 추가합니다:
httpIntelliJ IDEA 내에서 요청을 보내려면 옆에 있는 거터 아이콘(
)을 클릭합니다.
이것은 Services 도구 창에서 열리고 실행됩니다:

IntelliJ IDEA Ultimate에서는 HTTP 요청 파일에서 동일한 단계를 수행할 수 있습니다.
NOTE
라우트를 테스트하는 또 다른 방법은 Kotlin Notebook 내에서 khttp 라이브러리를 사용하는 것입니다.POST 요청을 위한 라우트 추가하기
이전 튜토리얼에서는 HTML 폼을 통해 작업을 생성했습니다. 하지만 이제 RESTful 서비스를 구축하고 있으므로 더 이상 그렇게 할 필요가 없습니다. 대신 대부분의 번거로운 작업을 대신 해줄 kotlinx.serialization 프레임워크를 활용할 것입니다.
src/main/kotlin 내부의 Routing.kt 파일을 엽니다.
다음과 같이
Application.configureRouting()함수에 새 POST 라우트를 추가합니다:kotlin다음의 새 import 문들을 추가합니다:
kotlinPOST 요청이
/tasks로 전송되면,kotlinx.serialization프레임워크가 요청 본문을Task객체로 변환하는 데 사용됩니다. 성공하면 작업이 레포지토리에 추가됩니다. 역직렬화 프로세스가 실패하면 서버는SerializationException을 처리하고, 작업이 중복된 경우IllegalStateException을 처리합니다.애플리케이션을 다시 시작합니다.
Postman에서 이 기능을 테스트하려면 URL
http://0.0.0.0:8080/tasks로 새 POST 요청을 만듭니다.Body 패널에 새 작업을 나타내는 다음 JSON 문서를 추가합니다:
json
Send을 클릭하여 요청을 보냅니다.
http://0.0.0.0:8080/tasks로 GET 요청을 보내 작업이 추가되었는지 확인할 수 있습니다.
IntelliJ IDEA Ultimate 내에서 HTTP 요청 파일에 다음 내용을 추가하여 동일한 단계를 수행할 수 있습니다:
http
삭제 기능 지원 추가하기
서비스에 기본 작업들을 거의 다 추가했습니다. 이러한 작업들은 종종 CRUD(Create, Read, Update, and Delete) 작업으로 요약됩니다. 이제 삭제(Delete) 작업을 구현하겠습니다.
TaskRepository.kt 파일에서
TaskRepository객체 내에 이름을 기반으로 작업을 제거하는 다음 메서드를 추가합니다:kotlinRouting.kt 파일을 열고 DELETE 요청을 처리할 엔드포인트를
routing()함수에 추가합니다:kotlin애플리케이션을 다시 시작합니다.
HTTP 요청 파일에 다음 DELETE 요청을 추가합니다:
httpIntelliJ IDEA 내에서 DELETE 요청을 보내려면 옆에 있는 거터 아이콘(
)을 클릭합니다.
Services 도구 창에서 응답을 확인할 수 있습니다:

Ktor Client로 유닛 테스트 생성하기
지금까지는 애플리케이션을 수동으로 테스트했지만, 이미 눈치채셨겠지만 이 방식은 시간이 많이 걸리고 규모를 확장하기 어렵습니다. 대신 내장된 client 객체를 사용하여 JSON을 가져오고 역직렬화하는
src/test/kotlin 안에 있는 ServerTest.kt 파일을 엽니다.
ServerTest.kt 파일의 내용을 다음으로 교체합니다:
kotlin서버에서 했던 것과 동일한 방식으로 Plugins에
ContentNegotiation및kotlinx.serialization플러그인을 설치해야 함에 유의하세요.build.gradle.kts 파일에 다음 종속성을 추가합니다:
kotlin
JsonPath로 유닛 테스트 생성하기
Ktor 클라이언트나 유사한 라이브러리로 서비스를 테스트하는 것은 편리하지만, 품질 보증(QA) 관점에서는 단점이 있습니다. JSON을 직접 처리하지 않는 서버는 JSON 구조에 대한 가정이 확실하다고 확신할 수 없습니다.
예를 들어 다음과 같은 가정들입니다:
- 실제로는
object가 사용되는데 값이array에 저장되고 있다고 가정하는 경우. - 속성이 실제로는
strings인데numbers로 저장되고 있다고 가정하는 경우. - 멤버가 선언된 순서대로 직렬화되지 않는데 순서대로 된다고 가정하는 경우.
서비스를 여러 클라이언트가 사용할 예정이라면 JSON 구조에 대한 확신을 갖는 것이 중요합니다. 이를 위해 Ktor Client를 사용하여 서버에서 텍스트를 가져온 다음 JSONPath 라이브러리를 사용하여 이 콘텐츠를 분석합니다.
build.gradle.kts 파일의
dependencies블록에 JSONPath 라이브러리를 추가합니다:kotlinsrc/test/kotlin 폴더로 이동하여 새 ApplicationJsonPathTest.kt 파일을 생성합니다.
ApplicationJsonPathTest.kt 파일을 열고 다음 내용을 추가합니다:
kotlinJsonPath 쿼리는 다음과 같이 작동합니다:
$[*].name은 "문서를 배열로 처리하고 각 항목의 name 속성 값을 반환하라"는 의미입니다.$[?(@.priority == '$priority')].name은 "우선순위가 제공된 값과 동일한 배열의 모든 항목에 대해 name 속성 값을 반환하라"는 의미입니다.
이와 같은 쿼리를 사용하여 반환된 JSON에 대한 이해가 올바른지 확인할 수 있습니다. 코드 리팩토링이나 서비스 재배포를 수행할 때, 현재 프레임워크에서의 역직렬화에 문제가 없더라도 직렬화 과정에서의 모든 변경 사항을 식별할 수 있습니다. 이를 통해 공개적으로 사용 가능한 API를 자신 있게 다시 배포할 수 있습니다.
다음 단계
축하합니다! 이제 Task Manager 애플리케이션을 위한 RESTful API 서비스를 성공적으로 완성했으며, Ktor Client와 JsonPath를 사용한 유닛 테스트의 세부 사항을 배웠습니다.
