Docs 菜单
Docs 主页
/ /

使用 Ktor 和MongoDB Atlas创建API

Ktor 是一个基于 Kotlin 的异步 Web框架,旨在构建现代 Web 应用程序和 API。它为使用Kotlin协程进行异步编程提供了强大的支持。

在本教程中,您将学习;了解如何使用 Ktor、 Kotlin驾驶员和MongoDB Atlas设立可运行的API 。

1

在开始本教程之前,请确保您具备以下条件:

  • 具有集群的MongoDB Atlas帐户。要查看说明,请参阅入门指南

  • IntelliJ IDEA.

2

使用 Ktor 项目生成器创建新的 Ktor项目。使用文本输入字段将项目工件命名为 com.mongodb.fitness-tracker。单击Configure 按钮并指定以下选项:

  • 构建系统:Gradle Kotlin

  • 引擎:Tomcat

  • 配置:HOCON 文件

使用 New Ktor Project 页面上的 Plugins 菜单,将以下插件添加到您的项目中:

  • 内容协商:在客户端和服务器之间协商媒体类型

  • GSON:提供序列化和反序列化支持

  • 路由:管理传入请求

  • Swagger:生成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.4")
// 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目录下创建以下三个包:

  • 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 包下创建 requestresponse 包,并在相应包中创建两个名为 FitnessRequest.ktFitnessResponse.kt 的文件。

将以下代码复制到 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. 设置 ContentNegotiation 插件以使用 Gson 格式化程序处理JSON序列化和反序列化

  2. 使用 Koin 启用依赖项注入

  3. 配置 Swagger 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

此文件定义了我们的API通过 /swagger-ui 端点提供的方法。

5

此步骤实施以下API端点:

  • GET /fitness/{id}:按ID检索适应度条目

  • POST /fitness:创建新的适应度条目

  • PATCH /fitness/{id}:更新指定的适应度条目

  • DELETE /fitness/{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 Vector Search存储库。

后退

验证驱动程序签名

在此页面上