はじめに
Kotlinは、その簡潔で安全な文法から、Android開発の公式言語として採用されていますが、その活躍の場はサーバーサイドにも広がっています。本記事で紹介するKtor(ケーター)は、Kotlinを開発するJetBrains社が自ら手掛ける、モダンなWebアプリケーションフレームワークです。
Ktorは、Kotlinの強力な非同期処理機能である「コルーチン」を最大限に活用し、軽量でありながらスケーラブルなアプリケーションを効率的に構築できるのが大きな特徴です。
この記事では、Kotlinの基本文法を理解しているWebフレームワーク初学者の方を対象に、Ktorの基本的な考え方から、実際に手を動かして簡単なCRUD(作成・読み取り・更新・削除)操作が可能なREST APIサーバーを構築するまでを、ステップバイステップで丁寧に解説します。
最短で課題解決する一冊
この記事の内容と高い親和性が確認できたベストマッチです。早めにチェックしておきましょう。
Ktorの主な特徴
Ktorが多くの開発者から注目されている理由には、以下のような特徴が挙げられます。
- 軽量かつ柔軟なプラグインアーキテクチャ: Ktorは最小限のコア機能のみを提供し、ルーティング、認証、JSONシリアライゼーションといった追加機能はすべて「プラグイン」として導入します。これにより、アプリケーションに必要な機能だけを選択でき、不必要な複雑さやオーバーヘッドを排除できます。
- コルーチンベースの非同期処理: Ktorは内部的にKotlinコルーチンを全面的に採用しています。これにより、スレッドをブロックすることなく大量のリクエストを効率的に処理でき、パフォーマンスの高い非同期アプリケーションを直感的に記述できます。
- JetBrainsによる公式サポート: Kotlin言語の開発元であるJetBrainsが直接開発しているため、言語の進化に迅速に対応し、安定したサポートが期待できます。IntelliJ IDEAとの親和性も抜群です。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
開発環境のセットアップ
それでは、早速プロジェクトを作成していきましょう。
前提条件
- JDK 11以上
- IntelliJ IDEA(無料のCommunity版で問題ありません)
Ktorプロジェクトジェネレーター
Ktorには、Webベースのプロジェクトジェネレーターが用意されており、これを使うのが最も簡単です。
- Ktor Project Generator にアクセスします。
- 左側のペインで以下のように設定します。
- Project Name:
ktor-task-apiなど、任意のプロジェクト名を入力します。 - Build System:
Gradle Kotlin DSLを選択します。 - Website:
com.exampleなど、任意の名前に変更します。 - Ktor Version: 最新のバージョンが選択されていることを確認します。
- Project Name:
- Pluginsセクションで「Add plugins」をクリックし、以下の3つのプラグインを検索して追加します。
Routing: URLに基づいてリクエストを処理するために必須のプラグインです。Content Negotiation: リクエストボディやレスポンスをJSONなどの形式に自動変換するために使います。kotlinx.serialization:Content Negotiationが実際にJSONの変換処理を行うために利用するライブラリです。
- 「Generate Project」ボタンをクリックすると、設定に基づいたプロジェクトが
.zip形式でダウンロードされます。 - ダウンロードしたzipファイルを解凍し、IntelliJ IDEAで開きます。
プロジェクト構造
IntelliJ IDEAでプロジェクトを開くと、以下のような主要なファイルとディレクトリが作成されています。
build.gradle.kts: プロジェクトの依存関係(ライブラリ)やプラグインの設定が記述されています。src/main/kotlin/com/example/Application.kt: アプリケーションのエントリーポイント(main関数)と、Ktorサーバーの設定(プラグインの組み込みなど)を行う中心的なファイルです。src/main/kotlin/com/example/plugins/Routing.kt: ルーティングの設定を記述するファイルです。src/main/kotlin/com/example/plugins/Serialization.kt: コンテンツネゴシエーションの設定を記述するファイルです。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
最初のAPIエンドポイント作成 (Hello World)
まずは、最も簡単なエンドポイントを作成して、サーバーが正しく動作することを確認しましょう。
src/main/kotlin/com/example/plugins/Routing.kt を開き、以下のように編集します。
package com.example.plugins
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
// GETリクエストがルートパス("/")に来たときに実行される
get("/") {
// "Hello Ktor!"というテキストをレスポンスとして返す
call.respondText("Hello Ktor!")
}
}
}サーバーの起動と確認
Application.ktファイルを開き、main関数の横にある緑色の再生ボタン(▶️)をクリックしてサーバーを起動します。- コンソールに
Application startedと表示されれば起動成功です(デフォルトではポート8080で起動します)。 - Webブラウザやターミナルから
curlコマンドでhttp://localhost:8080にアクセスします。
$ curl http://localhost:8080
Hello Ktor!画面またはターミナルに "Hello Ktor!" と表示されれば成功です。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
実践的なCRUD APIの実装
次に、タスク管理アプリケーションを想定した、より実践的なCRUD APIを実装していきます。
1. データクラスの作成
まず、APIで送受信するデータの構造を定義します。Taskというデータクラスを作成しましょう。
src/main/kotlin/com/example ディレクトリに Task.kt という新しいファイルを作成します。
package com.example
import kotlinx.serialization.Serializable
// @Serializableアノテーションを付けることで、
// kotlinx.serializationライブラリがこのクラスをJSONに変換できるようになる
@Serializable
data class Task(
val id: Int,
var title: String,
var done: Boolean
)2. Content Negotiationの設定
次に、KtorがTaskオブジェクトをJSONに自動変換できるように、Content Negotiationプラグインを設定します。
src/main/kotlin/com/example/plugins/Serialization.kt を開き、以下のように設定されていることを確認します。
package com.example.plugins
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
fun Application.configureSerialization() {
install(ContentNegotiation) {
// JSON形式のシリアライゼーション/デシリアライゼーションを有効にする
json()
}
}プロジェクトジェネレーターでプラグインを追加していれば、この設定は自動的に行われています。
3. ルーティングの定義 (CRUD)
それでは、CRUD操作に対応する各エンドポイントを Routing.kt に実装していきましょう。
今回は簡単のため、データベースの代わりにメモリ上のリストをデータストアとして使用します。
src/main/kotlin/com/example/plugins/Routing.kt を以下のように全面的に書き換えてください。
package com.example.plugins
import com.example.Task
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.util.concurrent.atomic.AtomicInteger
// タスクを保存するためのインメモリリスト
// 本来はデータベースを使用する
val tasks = mutableListOf(
Task(1, "Ktorプロジェクト作成", true),
Task(2, "CRUD API実装", false),
Task(3, "テストを書く", false)
)
// 新しいタスクIDを安全に採番するためのカウンタ
val idCounter = AtomicInteger(tasks.size)
fun Application.configureRouting() {
routing {
// C (Create): 新しいタスクを作成する
post("/tasks") {
// リクエストボディからTaskオブジェクトを受け取る
// JSONからTaskへの変換はContentNegotiationが自動で行う
val taskRequest = call.receive<TaskRequest>()
val newTask = Task(
id = idCounter.incrementAndGet(),
title = taskRequest.title,
done = taskRequest.done
)
tasks.add(newTask)
// 作成されたリソースとステータスコード201 (Created) を返す
call.respond(HttpStatusCode.Created, newTask)
}
// R (Read): 全てのタスクを取得する
get("/tasks") {
// tasksリストをJSON配列として返す
call.respond(tasks)
}
// R (Read): 指定したIDのタスクを取得する
get("/tasks/{id}") {
// パスパラメータからIDを取得する
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid ID format")
return@get // なぜなら: これ以降の処理を中断するため
}
val task = tasks.find { it.id == id }
if (task != null) {
call.respond(task)
} else {
call.respond(HttpStatusCode.NotFound, "Task not found")
}
}
// U (Update): 既存のタスクを更新する
put("/tasks/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid ID format")
return@put
}
val taskRequest = call.receive<TaskRequest>()
val task = tasks.find { it.id == id }
if (task != null) {
task.title = taskRequest.title
task.done = taskRequest.done
call.respond(task)
} else {
call.respond(HttpStatusCode.NotFound, "Task not found")
}
}
// D (Delete): タスクを削除する
delete("/tasks/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid ID format")
return@delete
}
if (tasks.removeIf { it.id == id }) {
// 削除が成功したことを示す
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(HttpStatusCode.NotFound, "Task not found")
}
}
}
}
// POSTやPUTで受け取るためのリクエスト用データクラス
// IDはサーバー側で採番するため、リクエストには含めない
@Serializable
data class TaskRequest(
val title: String,
val done: Boolean
)4. 動作確認
サーバーを再起動し、curl や Postman などのAPIクライアントツールを使って各エンドポイントの動作を確認してみましょう。
全タスク取得 (GET)
$ curl http://localhost:8080/tasks
[{"id":1,"title":"Ktorプロジェクト作成","done":true},{"id":2,"title":"CRUD API実装","done":false},{"id":3,"title":"テストを書く","done":false}]タスク作成 (POST)
$ curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"title":"動作確認","done":true}'
{"id":4,"title":"動作確認","done":true}タスク更新 (PUT)
$ curl -X PUT http://localhost:8080/tasks/2 \
-H "Content-Type: application/json" \
-d '{"title":"CRUD API実装完了!","done":true}'
{"id":2,"title":"CRUD API実装完了!","done":true}タスク削除 (DELETE)
$ curl -X DELETE http://localhost:8080/tasks/3
# レスポンスボディなし (ステータスコード 204 No Content)さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
まとめ
本記事では、Kotlin製のWebフレームワークKtorの基本的な概念と、実践的なCRUD APIサーバーの構築方法について解説しました。プラグインベースの柔軟なアーキテクチャと、コルーチンによる高いパフォーマンスを両立したKtorが、いかに効率的にサーバーサイドアプリケーションを開発できるかを感じていただけたかと思います。
今回作成したのはインメモリでデータを扱うシンプルなAPIでしたが、ここからさらに発展させることが可能です。
次のステップ:
- データベース接続:
Exposedやjasync-sqlなどのライブラリを使って、PostgreSQLやMySQLなどの永続的なデータストアに接続する。 - 認証機能:
Authenticationプラグインを導入し、JWTなどを用いたAPI認証を実装する。 - テスト:
ktor-server-test-hostを使って、作成したAPIエンドポイントの単体テストや結合テストを記述する。
Ktorは学習コストが比較的低く、Kotlinに慣れ親しんだ開発者であればすぐにでも強力な武器になります。ぜひ、公式ドキュメントなどを参考に、さらに理解を深めてみてください。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。





