Docs Menu
Docs Home
/ /

Ktor とMongoDB Atlasを使用してAPIを作成する

Ktor は、最新の Web アプリケーションと API を構築するために設計された Kotlin ベースの非同期 Webフレームワークです。 Kotlinコルーチンを使用した非同期プログラミングの堅牢なサポートを提供します。

このチュートリアルでは、Ktor、 Kotlinドライバー、 MongoDB Atlasを使用して実行可能なAPIを設定する方法を学習します。

1

このチュートリアルを開始する前に、以下があることを確認してください。

  • クラスターを持つMongoDB Atlasアカウント。手順を表示するには、「 開始ガイド 」を参照してください。

  • IntelliJ IDEA.

2

Ktor プロジェクト ジェネレーター を使用して新しい Ktorプロジェクトを作成します。テキスト入力フィールドを使用して、プロジェクトアーティファクトcom.mongodb.fitness-tracker に名前を付けます。 []Configure ボタンをクリックして、次のオプションを指定します。

  • ビルド システム: Gradle Kotlin

  • エンジン: Tools

  • 構成: MONGO ファイル

[New Ktor プロジェクト] ページの [Plugins] メニューを使用して、次のプラグインをプロジェクトに追加します。

  • コンテンツネゴシエート:クライアントとサーバー間でメディアタイプをネゴシエートします

  • GSON:直列化と逆直列化のサポートを提供

  • ルーティング: 受信リクエストを管理

  • Swedge: APIドキュメントを生成

[Generate Project] ボタンをクリックして、新しい Ktorプロジェクトを含む zipファイルをダウンロードします。

ダウンロードしたファイルを解凍し、プロジェクトディレクトリのルートディレクトリにある build.gradle.ktsファイルを開きます。次のプラグインを plugins ブロックに追加します。

plugins {
kotlin("jvm") version "1.9.22"
id("io.ktor.plugin") version "2.3.7"
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22"
}

次の依存関係を同じファイルの dependencies ブロックに追加します。

dependencies {
implementation("io.ktor:ktor-server-core-jvm")
implementation("io.ktor:ktor-server-swagger-jvm")
implementation("io.ktor:ktor-server-content-negotiation-jvm")
implementation("io.ktor:ktor-serialization-gson-jvm")
implementation("io.ktor:ktor-server-tomcat-jvm")
implementation("ch.qos.logback:logback-classic:$logback_version")
implementation("io.ktor:ktor-server-config-yaml:2.3.8")
// MongoDB
implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.6.1")
// Koin dependency injection
implementation("io.insert-koin:koin-ktor:3.5.3")
implementation("io.insert-koin:koin-logger-slf4j:3.5.3")
// Client
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
}
3

src/main/kotlinディレクトリに次の 3 つのパッケージを作成します。

  • application

  • domain

  • infrastructure

domainパッケージ内に entity という名前の新しいサブパッケージを作成し、entity 内に Fitness.kt という名前のファイルを作成します。次のコードをファイルにコピーします。

package com.mongodb.domain.entity
import com.mongodb.application.response.FitnessResponse
import org.bson.codecs.pojo.annotations.BsonId
import org.bson.types.ObjectId
data class Fitness(
@BsonId
val id: ObjectId,
val exerciseType: String,
val notes: String,
val details: FitnessDetails
){
fun toResponse() = FitnessResponse(
id = id.toString(),
exerciseType = exerciseType,
notes = notes,
details = details
)
}
data class FitnessDetails(
val durationMinutes: Int,
val distance: Double,
val caloriesBurned: Int
)

上記のコードの toResponse() メソッドは、FitnessResponseクラスをまだ作成していないため、エラーをスローします。 application/request パッケージの下に request パッケージと response パッケージを作成し、対応するパッケージに FitnessRequest.ktFitnessResponse.kt という名前の 2 つのファイルを作成します。

次のコードを FitnessRequest.ktファイルにコピーします。

package com.mongodb.application.response
import com.mongodb.domain.entity.FitnessDetails
data class FitnessResponse(
val id: String,
val exerciseType: String,
val notes: String,
val details: FitnessDetails
)

次のコードを FitnessResponse.ktファイルにコピーします。

package com.mongodb.application.response
import com.mongodb.domain.entity.FitnessDetails
data class FitnessResponse(
val id: String,
val exerciseType: String,
val notes: String,
val details: FitnessDetails
)

domainパッケージの下に portsパッケージを作成し、portsパッケージに FitnessRepository.kt という名前のファイルを作成します。このファイルは、データベースと通信するインターフェースを表します。次のコードを FitnessRepository.ktファイルにコピーします。

package com.mongodb.domain.ports
import com.mongodb.domain.entity.Fitness
import org.bson.BsonValue
import org.bson.types.ObjectId
interface FitnessRepository {
suspend fun insertOne(fitness: Fitness): BsonValue?
suspend fun deleteById(objectId: ObjectId): Long
suspend fun findById(objectId: ObjectId): Fitness?
suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long
}

infrastructureパッケージの下に repositoryパッケージを作成し、repositoryパッケージに FitnessRepositoryImpl.kt という名前のファイルを作成します。このファイルは、 インターフェースの メソッドを実装します。次のコードを FitnessRepositoryImpl.ktファイルにコピーします。

package com.mongodb.infrastructure.repository
import com.mongodb.MongoException
import com.mongodb.client.model.Filters
import com.mongodb.client.model.UpdateOptions
import com.mongodb.client.model.Updates
import com.mongodb.domain.entity.Fitness
import com.mongodb.domain.ports.FitnessRepository
import com.mongodb.kotlin.client.coroutine.MongoDatabase
import kotlinx.coroutines.flow.firstOrNull
import org.bson.BsonValue
import org.bson.types.ObjectId
class FitnessRepositoryImpl(
private val mongoDatabase: MongoDatabase
) : FitnessRepository {
companion object {
const val FITNESS_COLLECTION = "fitness"
}
override suspend fun insertOne(fitness: Fitness): BsonValue? {
try {
val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).insertOne(
fitness
)
return result.insertedId
} catch (e: MongoException) {
System.err.println("Unable to insert due to an error: $e")
}
return null
}
override suspend fun deleteById(objectId: ObjectId): Long {
try {
val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).deleteOne(Filters.eq("_id", objectId))
return result.deletedCount
} catch (e: MongoException) {
System.err.println("Unable to delete due to an error: $e")
}
return 0
}
override suspend fun findById(objectId: ObjectId): Fitness? =
mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).withDocumentClass<Fitness>()
.find(Filters.eq("_id", objectId))
.firstOrNull()
override suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long {
try {
val query = Filters.eq("_id", objectId)
val updates = Updates.combine(
Updates.set(Fitness::exerciseType.name, fitness.exerciseType),
Updates.set(Fitness::notes.name, fitness.notes),
Updates.set(Fitness::details.name, fitness.details)
)
val options = UpdateOptions().upsert(true)
val result =
mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION)
.updateOne(query, updates, options)
return result.modifiedCount
} catch (e: MongoException) {
System.err.println("Unable to update due to an error: $e")
}
return 0
}
}
4

以下のコードをApplication.kt ファイルに貼り付けてください。 このコードは、次のアクションを実行します。

  1. Gson フォーマッタを使用してJSON の直列化と逆直列プラグインを処理するように

  2. Koin を使用して依存関係挿入を有効にします

  3. Swedge APIルートを構成します。これは /swagger エンドポイントで利用可能です。

package com.mongodb
import com.mongodb.application.routes.fitnessRoutes
import com.mongodb.domain.ports.FitnessRepository
import com.mongodb.infrastructure.repository.FitnessRepositoryImpl
import com.mongodb.kotlin.client.coroutine.MongoClient
import io.ktor.serialization.gson.gson
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.plugins.swagger.swaggerUI
import io.ktor.server.routing.routing
import io.ktor.server.tomcat.EngineMain
import org.koin.dsl.module
import org.koin.ktor.plugin.Koin
import org.koin.logger.slf4jLogger
fun main(args: Array<String>): Unit = EngineMain.main(args)
fun Application.module() {
install(ContentNegotiation) {
gson {
}
}
install(Koin) {
slf4jLogger()
modules(module {
single { MongoClient.create(
environment.config.propertyOrNull("ktor.mongo.uri")?.getString() ?: throw RuntimeException("Failed to access MongoDB URI.")
) }
single { get<MongoClient>().getDatabase(environment.config.property("ktor.mongo.database").getString()) }
}, module {
single<FitnessRepository> { FitnessRepositoryImpl(get()) }
})
}
routing {
swaggerUI(path = "swagger-ui", swaggerFile = "openapi/documentation.yaml") {
version = "4.15.5"
}
fitnessRoutes()
}
}

application.confファイルを開き、以下を貼り付けます。

ktor {
deployment {
port = 8080
}
application {
modules = [ com.mongodb.ApplicationKt.module ]
}
mongo {
uri = ${?MONGO_URI}
database = ${?MONGO_DATABASE}
}
}

documentation.yamlファイルを開き、以下を貼り付けます。

openapi: 3.0.0
info:
title: Fitness API
version: 1.0.0
description: |
This Swagger documentation file outlines the API specifications for a Fitness Tracker application built with Ktor and MongoDB. The API allows users to manage fitness records including creating new records, updating and deleting records by ID. The API uses the Fitness and FitnessDetails data classes to structure the fitness-related information.
paths:
/fitness:
post:
summary: Create a new fitness record
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FitnessRequest'
responses:
'201':
description: Fitness created successfully
'400':
description: Bad request
/fitness/{id}:
get:
summary: Retrieve fitness record by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
example: {}
'404':
description: Fitness not found
delete:
summary: Delete fitness record by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Fitness deleted successfully
'400':
description: Bad request
'404':
description: Fitness not found
patch:
summary: Update fitness record by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FitnessRequest'
responses:
'200':
description: Fitness updated successfully
'400':
description: Bad request
'404':
description: Fitness not found
components:
schemas:
Fitness:
type: object
properties:
id:
type: string
format: uuid
exerciseType:
type: string
notes:
type: string
details:
$ref: '#/components/schemas/FitnessDetails'
required:
- id
- notes
- details
FitnessDetails:
type: object
properties:
durationMinutes:
type: integer
format: int32
distance:
type: number
format: double
caloriesBurned:
type: integer
format: int32
required:
- durationMinutes
- distance
- caloriesBurned
FitnessRequest:
type: object
properties:
exerciseType:
type: string
notes:
type: string
details:
$ref: '#/components/schemas/FitnessDetails'
required:
- exerciseType
- notes
- details

このファイルは、/swagger-ui エンドポイントを通じてAPIが提供するメソッドを定義します。

5

このステップでは、次のAPIエンドポイントを実装します。

  • GET /feeness/{id}: IDを使用して整合性エントリを取得します

  • POST /整合性: 新しい整合性エントリを作成します

  • PATCH /整合性/{id}: 指定された整合性エントリを更新します

  • DELETE /整合性/{id}: 指定された整合性エントリを削除します

applicationパッケージの下に routesパッケージを作成し、routesパッケージに FitnessRoutes.kt という名前のファイルを作成します。次のコードを FitnessRoutes.ktファイルにコピーします。

package com.mongodb.application.routes
import com.mongodb.application.request.FitnessRequest
import com.mongodb.application.request.toDomain
import com.mongodb.domain.ports.FitnessRepository
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.route
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.patch
import org.bson.types.ObjectId
import org.koin.ktor.ext.inject
fun Route.fitnessRoutes() {
val repository by inject<FitnessRepository>()
route("/fitness") {
post {
val fitness = call.receive<FitnessRequest>()
val insertedId = repository.insertOne(fitness.toDomain())
call.respond(HttpStatusCode.Created, "Created fitness with id $insertedId")
}
delete("/{id?}") {
val id = call.parameters["id"] ?: return@delete call.respondText(
text = "Missing fitness id",
status = HttpStatusCode.BadRequest
)
val delete: Long = repository.deleteById(ObjectId(id))
if (delete == 1L) {
return@delete call.respondText("Fitness Deleted successfully", status = HttpStatusCode.OK)
}
return@delete call.respondText("Fitness not found", status = HttpStatusCode.NotFound)
}
get("/{id?}") {
val id = call.parameters["id"]
if (id.isNullOrEmpty()) {
return@get call.respondText(
text = "Missing id",
status = HttpStatusCode.BadRequest
)
}
repository.findById(ObjectId(id))?.let {
call.respond(it.toResponse())
} ?: call.respondText("No records found for id $id")
}
patch("/{id?}") {
val id = call.parameters["id"] ?: return@patch call.respondText(
text = "Missing fitness id",
status = HttpStatusCode.BadRequest
)
val updated = repository.updateOne(ObjectId(id), call.receive())
call.respondText(
text = if (updated == 1L) "Fitness updated successfully" else "Fitness not found",
status = if (updated == 1L) HttpStatusCode.OK else HttpStatusCode.NotFound
)
}
}
}

上記のコードでは、 APIエンドポイントとそのハンドラーを定義します。

アプリケーションを実行中前に、HTTP.ktRouting.ktSerialization.kt ファイルを含む plugins フォルダーを削除します。これらはすでに Application.ktファイルに含まれているためです。

6

MongoDB Atlasクラスターから接続 URI を取得します。その方法の詳細については、 「 MongoDBへの接続 」ガイドの「 接続 URI 」セクションを参照してください。

application.confファイルを開き、次の構成値を追加します。

-DMONGO_URI= <your connection URI>
-DMONGO_DATABASE= <your database name>

IntelliJ の Application.ktファイルに移動し、Run ボタンをクリックします。ウェブ ブラウザで http://localhost:8080/swagger-ui/ に移動することでAPIにアクセスできるようになりました。

Ktor の詳細については、 Ktor のドキュメントを参照してください。

このチュートリアルのソースコードを表示するには、 GitHub のKotlin Ktor MongoDB ベクトル検索リポジトリ を参照してください。

戻る

ドライバー署名の検証

項目一覧