Se você procura um framework leve, idiomático e 100% Kotlin para construir APIs REST, o Ktor é a escolha certa. Criado pela JetBrains (a mesma empresa por trás do Kotlin), o Ktor aproveita o melhor da linguagem: coroutines nativas, DSLs elegantes e tipagem forte. Neste tutorial, vamos construir uma API REST completa do zero.
O que é o Ktor?
Ktor é um framework assíncrono para criar aplicações conectadas — servidores HTTP, clientes HTTP, microserviços e WebSockets. Diferente do Spring Boot, que traz um ecossistema gigante de convenções e auto-configuração, o Ktor segue a filosofia de ser minimalista e modular: você adiciona apenas o que precisa, sem mágica escondida.
As principais características do Ktor incluem:
- Assincronicidade nativa: construído sobre coroutines, cada requisição é uma coroutine — sem threads bloqueadas
- DSL idiomático: a configuração do servidor usa DSLs de Kotlin, resultando em código expressivo e legível
- Multiplataforma: o cliente HTTP do Ktor roda em JVM, Native, JavaScript e WebAssembly
- Modular: sistema de plugins onde você instala só o que precisa (serialização, autenticação, CORS, etc.)
- Transparente: sem anotações mágicas, sem injeção de dependência implícita, sem proxy de classes
Configurando o projeto
A maneira mais rápida de iniciar é pelo Ktor Project Generator, onde você seleciona os plugins desejados e baixa o projeto pronto. Alternativamente, configure manualmente com Gradle:
// build.gradle.kts
plugins {
kotlin("jvm") version "2.1.0"
kotlin("plugin.serialization") version "2.1.0"
id("io.ktor.plugin") version "3.1.1"
}
dependencies {
implementation("io.ktor:ktor-server-core")
implementation("io.ktor:ktor-server-netty")
implementation("io.ktor:ktor-server-content-negotiation")
implementation("io.ktor:ktor-serialization-kotlinx-json")
implementation("io.ktor:ktor-server-status-pages")
implementation("ch.qos.logback:logback-classic:1.5.15")
testImplementation("io.ktor:ktor-server-test-host")
testImplementation("org.jetbrains.kotlin:kotlin-test")
}
O plugin io.ktor.plugin facilita o build e gera o fat JAR para deploy automaticamente.
Hello World com Ktor
Vamos criar o servidor mais simples possível:
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondText("Olá, Ktor!")
}
}
}.start(wait = true)
}
Execute e acesse http://localhost:8080 — pronto, seu servidor Ktor está rodando. Note como tudo é explícito: você vê o engine (Netty), a porta, as rotas. Nada acontece sem que você configure.
Roteamento com a DSL do Ktor
O sistema de roteamento do Ktor usa uma DSL expressiva que suporta agrupamento, parâmetros de caminho e todos os métodos HTTP:
fun Application.configureRouting() {
routing {
route("/api/v1") {
get("/tarefas") {
// Listar todas as tarefas
}
get("/tarefas/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
?: return@get call.respondText(
"ID inválido",
status = HttpStatusCode.BadRequest
)
// Buscar tarefa por ID
}
post("/tarefas") {
// Criar nova tarefa
}
put("/tarefas/{id}") {
// Atualizar tarefa existente
}
delete("/tarefas/{id}") {
// Remover tarefa
}
}
}
}
A função route("/api/v1") agrupa todas as rotas sob um prefixo comum, evitando repetição. Os parâmetros de caminho como {id} são acessados via call.parameters.
Serialização JSON com kotlinx.serialization
Para trabalhar com JSON, o Ktor se integra nativamente com o kotlinx.serialization. Primeiro, instale o plugin de Content Negotiation:
fun Application.configureSerialization() {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
Agora defina seus modelos de dados com a anotação @Serializable:
import kotlinx.serialization.Serializable
@Serializable
data class Tarefa(
val id: Int = 0,
val titulo: String,
val descricao: String = "",
val concluida: Boolean = false
)
@Serializable
data class TarefaRequest(
val titulo: String,
val descricao: String = ""
)
Exemplo completo: API CRUD de tarefas
Agora vamos juntar tudo em uma API CRUD funcional. Usaremos uma lista em memória para simplificar (em produção, você conectaria a um banco de dados com Exposed ou ktorm):
import io.ktor.http.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.util.concurrent.atomic.AtomicInteger
val tarefas = mutableListOf<Tarefa>()
val idCounter = AtomicInteger(0)
fun Application.configureTarefasRoutes() {
routing {
route("/api/tarefas") {
// GET — listar todas
get {
call.respond(tarefas)
}
// GET — buscar por ID
get("/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
val tarefa = tarefas.find { it.id == id }
if (tarefa == null) {
call.respond(HttpStatusCode.NotFound, "Tarefa não encontrada")
} else {
call.respond(tarefa)
}
}
// POST — criar nova
post {
val request = call.receive<TarefaRequest>()
val novaTarefa = Tarefa(
id = idCounter.incrementAndGet(),
titulo = request.titulo,
descricao = request.descricao
)
tarefas.add(novaTarefa)
call.respond(HttpStatusCode.Created, novaTarefa)
}
// PUT — atualizar existente
put("/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
val index = tarefas.indexOfFirst { it.id == id }
if (index == -1) {
call.respond(HttpStatusCode.NotFound, "Tarefa não encontrada")
} else {
val request = call.receive<TarefaRequest>()
val atualizada = tarefas[index].copy(
titulo = request.titulo,
descricao = request.descricao
)
tarefas[index] = atualizada
call.respond(atualizada)
}
}
// DELETE — remover
delete("/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
val removido = tarefas.removeAll { it.id == id }
if (removido) {
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(HttpStatusCode.NotFound, "Tarefa não encontrada")
}
}
}
}
}
Plugins essenciais
O Ktor funciona com um sistema de plugins que você instala conforme a necessidade. Além do ContentNegotiation que já vimos, os mais importantes são:
StatusPages — tratamento centralizado de erros:
install(StatusPages) {
exception<IllegalArgumentException> { call, cause ->
call.respond(HttpStatusCode.BadRequest, cause.localizedMessage)
}
exception<Throwable> { call, cause ->
call.respond(
HttpStatusCode.InternalServerError,
"Erro interno: ${cause.localizedMessage}"
)
}
}
CORS — controle de acesso cross-origin:
install(CORS) {
allowHost("meusite.com.br")
allowHeader(HttpHeaders.ContentType)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
}
CallLogging — logs estruturados de cada requisição:
install(CallLogging) {
level = Level.INFO
filter { call -> call.request.path().startsWith("/api") }
}
Outros plugins populares incluem Authentication (JWT, OAuth, Session), WebSockets, Rate Limiting e Compression.
Ktor vs Spring Boot: quando usar cada um?
Essa e uma das perguntas mais comuns entre devs Kotlin no backend. A resposta depende do contexto:
| Critério | Ktor | Spring Boot |
|---|---|---|
| Filosofia | Minimalista, explícito | Convenção sobre configuração |
| Curva de aprendizado | Mais simples para quem sabe Kotlin | Requer conhecer o ecossistema Spring |
| Performance | Mais leve, menos overhead | Mais pesado, mas altamente otimizado |
| Ecossistema | Menor, mas crescendo | Gigantesco, maduro |
| Multiplataforma | Sim (cliente HTTP) | Apenas JVM |
| Ideal para | Microserviços, APIs leves, serverless | Aplicações enterprise, monolitos |
Se você vem do mundo Java e já conhece Spring, a combinação Kotlin + Spring Boot oferece produtividade imediata. Se quer algo 100% Kotlin, sem heranças do mundo Java, o Ktor é o caminho natural. Para deploy serverless na AWS Lambda, o Ktor se destaca pelo cold start mais rápido.
Deploy: fat JAR para produção
O plugin do Ktor gera um fat JAR automaticamente. Basta rodar:
./gradlew buildFatJar
O JAR fica em build/libs/seu-projeto-all.jar. Para executar em produção:
java -jar build/libs/seu-projeto-all.jar
Para containerizar com Docker, um Dockerfile simples funciona:
FROM eclipse-temurin:21-jre-alpine
COPY build/libs/seu-projeto-all.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
O Ktor também suporta GraalVM Native Image para builds nativos com cold start de milissegundos — ideal para ambientes Kubernetes e serverless.
Testando a API
Ktor tem um módulo de testes embutido que permite testar rotas sem subir o servidor de verdade:
@Test
fun `deve criar tarefa com sucesso`() = testApplication {
application {
configureSerialization()
configureTarefasRoutes()
}
val response = client.post("/api/tarefas") {
contentType(ContentType.Application.Json)
setBody("""{"titulo": "Estudar Ktor", "descricao": "Completar tutorial"}""")
}
assertEquals(HttpStatusCode.Created, response.status)
}
O testApplication cria um ambiente de teste completo sem abrir portas de rede, tornando os testes rapidos e isolados.
Conclusao
O Ktor e um framework poderoso que abraca a filosofia do Kotlin: conciso, seguro e explícito. Ele é perfeito para microserviços, APIs REST e aplicações onde você quer controle total sobre o que acontece. Combinado com coroutines para concorrência e kotlinx.serialization para JSON, voce tem um stack moderno e performático para o backend.
Se está começando com Kotlin no servidor, o Ktor é uma excelente porta de entrada — mais acessível que o Spring Boot e com uma experiência de desenvolvimento verdadeiramente Kotlin-first. Se quiser comparar com frameworks de outras linguagens, veja como Go lida com APIs usando Gin e Echo ou como Python aborda o problema com FastAPI e Django.