Docs Menu
Docs Home
/ /

Crea una API utilizando Ktor y MongoDB Atlas

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.

1

Antes de comenzar este tutorial, asegúrese de tener lo siguiente:

  • Una cuenta de MongoDB Atlas con un clúster. Para ver las instrucciones, consulte la guía de introducción.

  • IntelliJ IDEA.

2

Cree un nuevo proyecto de Ktor con el Generador de Proyectos de Ktor. Asigne un nombre al artefacto del proyecto. com.mongodb.fitness-tracker utilizando el campo de entrada de texto. Haga clic en el 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

Haga clic en el botón Generate Project para descargar un archivo ZIP que contiene su nuevo proyecto Ktor.

Descomprima el archivo descargado y abra el archivo build.gradle.kts en el directorio raíz del proyecto. Agregue los siguientes complementos 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.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

Cree los siguientes tres paquetes en el directorio src/main/kotlin:

  • application

  • domain

  • infrastructure

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(
@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
)

El método toResponse() del código anterior genera un error porque aún no se ha creado la clase FitnessResponse. Cree los paquetes request y response dentro del paquete application/request y cree dos archivos llamados FitnessRequest.kt y FitnessResponse.kt en el paquete correspondiente.

Copie 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
)

Copie 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
)

Cree un paquete ports dentro del paquete domain y cree un archivo llamado FitnessRepository.kt dentro del 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
}

Cree un paquete repository dentro del paquete infrastructure y un archivo llamado FitnessRepositoryImpl.kt dentro del 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
}
}
4

Pegue el siguiente código en el archivo Application.kt. Este código realiza las siguientes acciones:

  1. Configura el complemento ContentNegotiation para manejar la serialización y deserialización de JSON mediante el formateador Gson

  2. Permite la inyección de dependencia mediante el uso de Koin

  3. Configura la ruta de la API Swagger, que está disponible en el punto final /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.

5

Este paso implementa los siguientes puntos finales de 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 aptitud 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 puntos finales de la API y sus controladores.

Antes de ejecutar la aplicación, elimine la carpeta plugins que contiene los archivos HTTP.kt, Routing.kt y Serialization.kt, porque ya los hemos incluido en el archivo Application.kt.

6

Recupere la URI de conexión de su clúster de MongoDB Atlas. Para obtener más información sobre cómo hacerlo, consulte Sección URI de conexión de la guía Conectarse a MongoDB.

Abra el archivo application.conf y agregue los siguientes valores de configuración:

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

Acceda al archivo Application.kt en IntelliJ y haga clic en el botón Run. Ahora puede acceder a la API accediendo a http://localhost:8080/swagger-ui/ en un navegador web.

Para obtener más información sobre Ktor, consulte la documentación de Ktor.

Para ver el código fuente de este tutorial, consulte el repositorio Kotlin Ktor MongoDB Vector Search en GitHub.

Volver

Validar las firmas de los conductores

En esta página