Skip to main content

πŸš€ Introduction to Ktor

πŸ“– What is Ktor?​

Ktor is a lightweight asynchronous web framework built with Kotlin. It allows you to easily create fast and efficient servers based on coroutines!

πŸ’‘ Creating a Project​

build.gradle.kts​

plugins {
kotlin("jvm") version "1.9.0"
id("io.ktor.plugin") version "2.3.5"
}

dependencies {
implementation("io.ktor:ktor-server-core:2.3.5")
implementation("io.ktor:ktor-server-netty:2.3.5")
implementation("ch.qos.logback:logback-classic:1.4.11")
}

🎯 First Server​

Hello World​

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("Hello, Ktor!")
}
}
}.start(wait = true)
}

After running, visit http://localhost:8080 in your browser!

Multiple Routes​

fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondText("ν™ˆνŽ˜μ΄μ§€")
}

get("/hello") {
call.respondText("μ•ˆλ…•ν•˜μ„Έμš”!")
}

get("/about") {
call.respondText("μ†Œκ°œ νŽ˜μ΄μ§€")
}
}
}.start(wait = true)
}

🎨 Routing​

Path Parameters​

fun Application.configureRouting() {
routing {
get("/user/{id}") {
val id = call.parameters["id"]
call.respondText("μ‚¬μš©μž ID: $id")
}

get("/product/{category}/{id}") {
val category = call.parameters["category"]
val id = call.parameters["id"]
call.respondText("μΉ΄ν…Œκ³ λ¦¬: $category, μƒν’ˆ ID: $id")
}
}
}

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

// 접속: http://localhost:8080/user/123
// 좜λ ₯: μ‚¬μš©μž ID: 123

Query Parameters​

routing {
get("/search") {
val query = call.request.queryParameters["q"]
val page = call.request.queryParameters["page"]

call.respondText("검색어: $query, νŽ˜μ΄μ§€: $page")
}
}

// 접속: http://localhost:8080/search?q=kotlin&page=1
// 좜λ ₯: 검색어: kotlin, νŽ˜μ΄μ§€: 1

πŸ”§ JSON Responses​

Setting up ContentNegotiation​

plugins {
// build.gradle.kts에 μΆ”κ°€
implementation("io.ktor:ktor-server-content-negotiation:2.3.5")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.5")
}

JSON Responses​

import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.Serializable

@Serializable
data class User(val id: Int, val name: String, val email: String)

fun Application.module() {
install(ContentNegotiation) {
json()
}

routing {
get("/user/{id}") {
val id = call.parameters["id"]?.toInt() ?: 0
val user = User(id, "홍길동", "hong@example.com")
call.respond(user)
}

get("/users") {
val users = listOf(
User(1, "홍길동", "hong@example.com"),
User(2, "κΉ€μ² μˆ˜", "kim@example.com")
)
call.respond(users)
}
}
}

🎯 Practical Example​

Simple API​

@Serializable
data class Product(
val id: Int,
val name: String,
val price: Double
)

fun Application.productAPI() {
install(ContentNegotiation) {
json()
}

val products = mutableListOf(
Product(1, "λ…ΈνŠΈλΆ", 1500000.0),
Product(2, "마우슀", 30000.0),
Product(3, "ν‚€λ³΄λ“œ", 80000.0)
)

routing {
// 전체 μƒν’ˆ 쑰회
get("/products") {
call.respond(products)
}

// νŠΉμ • μƒν’ˆ 쑰회
get("/products/{id}") {
val id = call.parameters["id"]?.toInt()
val product = products.find { it.id == id }

if (product != null) {
call.respond(product)
} else {
call.respondText("μƒν’ˆμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€", status = HttpStatusCode.NotFound)
}
}
}
}

fun main() {
embeddedServer(Netty, port = 8080) {
productAPI()
}.start(wait = true)
}

Status Codes​

import io.ktor.http.*

routing {
get("/status/ok") {
call.respondText("정상", status = HttpStatusCode.OK)
}

get("/status/created") {
call.respondText("생성됨", status = HttpStatusCode.Created)
}

get("/status/not-found") {
call.respondText("μ—†μŒ", status = HttpStatusCode.NotFound)
}

get("/status/error") {
call.respondText("μ„œλ²„ 였λ₯˜", status = HttpStatusCode.InternalServerError)
}
}

πŸ”₯ Middleware​

Logging​

import io.ktor.server.plugins.callloging.*

fun Application.module() {
install(CallLogging)

routing {
get("/") {
call.respondText("Hello!")
}
}
}

// μ½˜μ†”μ— μš”μ²­ 둜그 좜λ ₯

CORS Configuration​

import io.ktor.server.plugins.cors.routing.*

fun Application.module() {
install(CORS) {
anyHost() // λͺ¨λ“  호슀트 ν—ˆμš© (개발 ν™˜κ²½)
allowHeader(HttpHeaders.ContentType)
}

routing {
get("/api/data") {
call.respond(mapOf("message" to "CORS 섀정됨"))
}
}
}

πŸ› οΈ Application Structure​

Modularization​

// routes/UserRoutes.kt
fun Route.userRoutes() {
route("/users") {
get {
call.respond(listOf("User1", "User2"))
}

get("/{id}") {
val id = call.parameters["id"]
call.respondText("User $id")
}
}
}

// routes/ProductRoutes.kt
fun Route.productRoutes() {
route("/products") {
get {
call.respond(listOf("Product1", "Product2"))
}
}
}

// Application.kt
fun Application.module() {
install(ContentNegotiation) {
json()
}

routing {
userRoutes()
productRoutes()
}
}

πŸ€” Frequently Asked Questions​

Q1. Ktor vs. Spring Boot?​

A: Ktor is lightweight and coroutine-friendly!

// Ktor - κ°„κ²°ν•˜κ³  가벼움
fun Application.module() {
routing {
get("/hello") {
call.respondText("Hello!")
}
}
}

// Spring Boot - 더 λ§Žμ€ κΈ°λŠ₯κ³Ό μƒνƒœκ³„
@RestController
class HelloController {
@GetMapping("/hello")
fun hello() = "Hello!"
}

Q2. How do I change the port?​

A: Specify it when creating the server!

// μ½”λ“œμ—μ„œ
embeddedServer(Netty, port = 3000) {
// ...
}.start(wait = true)

// application.conf 파일
ktor {
deployment {
port = 3000
}
}

Q3. How do I serve static files?​

A: Use the Static plugin!

import io.ktor.server.http.content.*

routing {
static("/static") {
resources("static")
}

static("/files") {
files("uploads")
}
}

🎬 Conclusion​

Get started with server development quickly using Ktor!

핡심 정리:
βœ… κ²½λŸ‰ 비동기 ν”„λ ˆμž„μ›Œν¬
βœ… 코루틴 기반
βœ… κ°„λ‹¨ν•œ λΌμš°νŒ…
βœ… JSON 직렬화 지원
βœ… λͺ¨λ“ˆν™” κ°€λŠ₯

Next Step: Build a complete API in REST API!