Overview
Ktor es un framework web asíncrono basado en Kotlin, diseñado para crear aplicaciones web y API modernas. Ofrece un sólido soporte para la programación asíncrona con corrutinas de Kotlin.
En este tutorial, aprenderá a configurar una API ejecutable utilizando Ktor, el controlador Kotlin y MongoDB Atlas.
Tutorial
Verifica los requisitos previos
Antes de comenzar este tutorial, asegúrate de tener lo siguiente:
Una cuenta de MongoDB Atlas con un clúster. Para ver las instrucciones, consulte la guía de introducción.
Configura un proyecto Ktor
Crea un nuevo proyecto de Ktor usando el Generador de Proyectos Ktor. Nombra el artefacto del proyecto com.mongodb.fitness-tracker usando el campo de entrada de texto. Haz clic en Configure botón y especifique las siguientes opciones:
Sistema de compilación: Gradle Kotlin
Motor: Tomcat
Configuración: Archivo HOCON
Usando el menú Plugins en la página Nuevo proyecto de Ktor, agregue los siguientes complementos a su proyecto:
Negociación de contenido: Negocia los tipos de medios entre el cliente y el servidor
GSON: Proporciona soporte de serialización y deserialización
Enrutamiento: gestiona las solicitudes entrantes
Swagger: genera documentación de API
Haz clic en el botón Generate Project para descargar un archivo ZIP que contiene tu nuevo proyecto Ktor.
Descomprime el archivo descargado y abre el archivo build.gradle.kts en el directorio raíz del Proyecto. Añade los siguientes plugins al bloque 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" }
Agregue las siguientes dependencias al bloque dependencies en el mismo archivo:
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") }
Implementar operaciones CRUD
Crea los siguientes tres paquetes bajo el directorio src/main/kotlin:
applicationdomaininfrastructure
Dentro del paquete domain, crea un subpaquete llamado entity y, a continuación, crea un archivo dentro de entity llamado Fitness.kt. Copia el siguiente código en el archivo:
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( 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 )
El método toResponse() en el código anterior arroja un error porque aún no has creado la clase FitnessResponse. Crea paquetes request y response debajo del paquete application/request y crea dos archivos llamados FitnessRequest.kt y FitnessResponse.kt en el paquete correspondiente.
Copia el siguiente código en el archivo 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 )
Copia el siguiente código en el archivo 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 )
Crear un paquete ports bajo el paquete domain y crear un archivo llamado FitnessRepository.kt en el paquete ports. Este archivo representa la interfaz que se comunicará con la base de datos. Copie el siguiente código en el archivo 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 }
Crea un paquete repository dentro del paquete infrastructure y crea un archivo llamado FitnessRepositoryImpl.kt en el paquete repository. Este archivo implementa los métodos de la interfaz. Copie el siguiente código en el archivo 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 } }
Configurar complementos
Pegue el siguiente código en el archivo Application.kt. Este código realiza las siguientes acciones:
Configura el complemento ContentNegotiation para manejar la serialización y deserialización de JSON mediante el formateador Gson
Permite la inyección de dependencias utilizando Koin
Configura la ruta de Swagger API, que está disponible en el endpoint
/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() } }
Abra el archivo application.conf y pegue lo siguiente:
ktor { deployment { port = 8080 } application { modules = [ com.mongodb.ApplicationKt.module ] } mongo { uri = ${?MONGO_URI} database = ${?MONGO_DATABASE} } }
Abra el archivo documentation.yaml y pegue lo siguiente:
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
Este archivo define qué métodos proporciona nuestra API a través del punto final /swagger-ui.
Implementar puntos finales de API
Este paso implementa los siguientes puntos finales de la API:
GET /fitness/{id}: recupera una entrada de fitness por su ID
POST /fitness: Crea una nueva entrada de fitness
PATCH /fitness/{id}: Actualiza la entrada de fitness especificada
ELIMINAR /fitness/{id}: Elimina la entrada de fitness especificada
Cree un paquete routes dentro del paquete application y cree un archivo llamado FitnessRoutes.kt dentro del paquete routes. Copie el siguiente código en el archivo 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 ) } } }
El código anterior define los endpoints de la API y sus controladores.
Antes de ejecutar la aplicación, elimina la carpeta plugins que contiene los archivos HTTP.kt, Routing.kt y Serialization.kt, ya que ya los hemos incluido en el archivo Application.kt.
Ejecutar la aplicación
Recupere su URI de conexión del clúster de MongoDB Atlas. Para más información sobre cómo hacer esto, consulta la URI de conexión sección de la guía Conectar a MongoDB.
Abre el archivo application.conf y añade los siguientes valores de configuración:
-DMONGO_URI= <your connection URI> -DMONGO_DATABASE= <your database name>
Navegue hasta el archivo Application.kt en IntelliJ y haga clic en el botón Run. Ahora puedes acceder a la API navegando a http://localhost:8080/swagger-ui/ en un navegador web.
Información Adicional
Para obtener más información sobre Ktor, consulte la documentación de Ktor.
Para ver el código fuente de este tutorial, consulta el repositorio Kotlin Ktor MongoDB Vector Search en GitHub.