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…

¿Qué es Kotlin Multiplataforma?

¿Qué es Kotlin Multiplataforma?

En el mundo actual, donde los dispositivos móviles están presentes en nuestra vida diaria, es fundamental para los desarrolladores crear aplicaciones que se adapten a diferentes sistemas operativos. Kotlin Multiplataforma es una herramienta que facilita la creación de...

5 trucos de Kotlin para escribir código más eficiente en Android

5 trucos de Kotlin para escribir código más eficiente en Android

El lenguaje de programación Kotlin se ha convertido en el más popular para el desarrollo de aplicaciones de Android en los últimos años. Su sintaxis concisa y moderna, junto con su capacidad para mejorar la eficiencia de código, lo convierten en una opción atractiva...

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...

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 *