Cómo crear un backend en Kotlin usando Ktor
Antonio Leiva

Ktor es un framework de servidor web ligero y rápido para Kotlin, desarrollado por JetBrains.

Es ideal para crear aplicaciones web y servicios RESTful, y es muy fácil de usar y configurar.

En este artículo, vamos a ver cómo crear un backend para una aplicación de notas utilizando Ktor.

Crear el proyecto de Ktor

Para crearlo, lo más sencillo es ir a start.ktor.io, y crear un proyecto.

Elige el plugin de Content Negotiation.

Una vez finalices el proceso, se descargará un zip que necesitas descomprimir.

Abre la carpeta descomprimida desde IntelliJ.

Crea la clase de datos

Lo primero es el modelo del servidor. Para ello vamos a crear una clase anotada con @Serializable, para que la librería de content negotiation se capaz de convertirla a Json y viceversa.

@Serializable
data class Note(val id: Int, val title: String, val content: String)

Asegúrate de que tienes el plugin de Kotlin Serialization correctamente configurado.

plugins {
    ...
    kotlin("plugin.serialization") version "1.7.20"
}

Primer endpoint

Ahora podemos crear nuestra primera ruta en nuestro backend. En Ktor, las rutas son simplemente funciones que manejan solicitudes HTTP. Vamos a crear una ruta que maneje solicitudes GET a la ruta /notes y devuelva una lista de notas en formato JSON. Para hacer esto, vamos a crear un objeto NotesRepository con una función getAll:

object NotesRepository {

    fun getAll(): List<Note> {
        val notes = (0..10).map {
            Note(
                "Title $it",
                "Description $it",
                if (it % 3 == 0) Type.AUDIO else Type.TEXT
            )
        }
        return notes
    }
}

Ahora tenemos que enlazar esta función a una ruta en nuestro servidor Ktor.

Para ello, vamos a crear una clase Application y añadir un método configureRouting que contenga nuestras rutas:

class Application {

    fun Application.configureRouting() {
        route("/notes") {
            get { call.respond(NotesRepository.getAll()) }
        }
    }
}

Configura la serialización

Vamos a crear otra función configureSerialization() que identifica cómo se va a hacer la conversión entre el objeto y el resultado, que será en Json:

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json(Json {
            prettyPrint = true
        })
    }
}

Crear el servidor Ktor

Por último, necesitamos crear una instancia de nuestra clase Application y arrancar el servidor Ktor. Para ello, vamos a crear una función main en nuestro archivo Main.kt:

fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
        configureRouting()
        configureSerialization()
    }.start(wait = true)
}

Con esto, ya tenemos nuestro servidor Ktor en funcionamiento y nuestra ruta /notes lista para recibir solicitudes GET. Si abrimos nuestro navegador y vamos a la dirección http://localhost:8080/notes, deberíamos ver el listado de notas en formato JSON.

Configurar el resto de endpoints

Para crear un servidor sencillo, podemos almacenar las notas en memoria, guardadas en una lista, y configurar todos los endpoints basados en esto:

Create

Permite añadir nuevas notas. Devolverá un error en el caso en que la serialización falle:

post {
    try {
        val note = call.receive<Note>()
        val savedNote = NotesRepository.save(note)
        call.respond(HttpStatusCode.Created, savedNote)
    } catch (e: Exception) {
        call.respond(
            HttpStatusCode.BadRequest,
            "Bad JSON Data Body: ${e.message.toString()}"
        )
    }
}

Read

Ya teníamos una función que devolvía todas las notas, a la que vamos a añadir otra que devuelva una por id:

get {
    call.respond(NotesRepository.getAll())
}

get("{id}") {
    val id = call.parameters["id"] ?: return@get call.respond(
        HttpStatusCode.BadRequest,
        "Missing or malformed id"
    )

    val note =
        NotesRepository.getById(id.toLong()) ?: return@get call.respond(
            HttpStatusCode.NotFound,
            "No note with id $id"
        )

    call.respond(note)
}

Update

Actualiza el contenido de una nota. También lanza un error en el caso de que la nota no pueda serializarse correctamente desde el Json.

Además, lanzará otro error si la nota a actualizar no existe:

put {
    try {
        val note = call.receive<Note>()
        if (NotesRepository.update(note)) {
            call.respond(note)
        } else {
            call.respond(
                HttpStatusCode.NotFound,
                "No note with id ${note.id}"
            )
        }
    } catch (e: Exception) {
        call.respond(
            HttpStatusCode.BadRequest,
            "Bad JSON Data Body: ${e.message.toString()}"
        )
    }
}

Delete

Borra una nota. Lanzará errores si no consigue parsear el identificador de la nota, o si la nota no existe:

delete("{id}") {
    val id = call.parameters["id"] ?: return@delete call.respond(
        HttpStatusCode.BadRequest,
        "Missing or malformed id"
    )

    if (NotesRepository.delete(id.toLong())) {
        call.respond(HttpStatusCode.Accepted)
    } else {
        call.respond(
            HttpStatusCode.NotFound,
            "No order with id $id"
        )
    }
}

Repositorio completo

Aquí te dejo el repositorio de notas con todas las operaciones. Sería interesante en un proyecto real que los datos se persistieran en una base de datos, pero este punto lo dejo para otro artículo:

object NotesRepository {

    private val list = mutableListOf<Note>()
    private var currentId = 1L

    fun save(note: Note): Note =
        note.copy(id = currentId++)
            .apply(list::add)

    fun getAll(): List<Note> = list

    fun getById(id: Long): Note? = list.find { it.id == id }

    fun update(note: Note): Boolean =
        list.indexOfFirst { it.id == note.id }
            .takeIf { it >= 0 }
            ?.also { list[it] = note }
            .let { it != null }

    fun delete(id: Long): Boolean =
        list.indexOfFirst { it.id == id }
            .takeIf { it >= 0 }
            ?.also { list.removeAt(it) }
            .let { it != null }
}

Conclusión

En conclusión, Ktor es un marco de servidor ligero y fácil de usar para crear aplicaciones web y servicios REST en Kotlin.

Con su soporte para una amplia variedad de bases de datos y su facilidad de configuración y uso, Ktor es una excelente opción para desarrolladores que buscan una forma rápida y sencilla de crear un servidor para sus aplicaciones.

Además, gracias a su integración con otros frameworks y herramientas populares, como Hibernate y Exposed, Ktor es altamente personalizable y se puede adaptar a una amplia variedad de usos y necesidades.

En resumen, si estás buscando una forma sencilla de crear un servidor en Kotlin, Ktor es una excelente opción a tener en cuenta.

Quizá también te interese…

Cómo simular una base de datos reactiva en Room con Fakes

Cómo simular una base de datos reactiva en Room con Fakes

En el desarrollo de aplicaciones móviles es muy común utilizar bases de datos para almacenar y gestionar la información que se utiliza en la aplicación. En el caso de Android, una de las opciones más populares es Room, una librería de persistencia de datos que...

Flows de Kotlin para implementar búsquedas en tiempo real

Flows de Kotlin para implementar búsquedas en tiempo real

En Android, los Flows de Kotlin son una manera de representar secuencias de datos asincrónicas que emiten valores de forma continua. Estos Flows pueden ser útiles en situaciones en las que deseamos escuchar eventos y procesar los resultados de forma asíncrona, como en...

Kata del TicTacToe en Kotlin

Kata del TicTacToe en Kotlin

Escribe el código para representar una entidad que almacene el tablero de juego del 3 en raya, y que además tenga: Un método move() con 2 parámetros, fila y columna, que permita añadir un movimiento al tablero Un método findWinner(), que devuelva el ganador (X, Y o...

0 comentarios

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de DevExpert, S.L.U., con el fin de gestionar los comentarios que realizas en este blog. La legitimación se realiza a través del consentimiento de la parte interesada. Si no se acepta, no podrás comentar en este blog. Los datos que proporciona solo se utilizan para evitar el correo no deseado y no se usarán para nada más. Puede ejercer los derechos de acceso, rectificación, cancelación y oposición en contacto@devexperto.com.

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acepto la política de privacidad *