Skip to main content
kotlin-ktor-patterns Ktor server patterns: routing DSL, plugins, authentication, Koin DI, kotlinx.serialization, WebSockets, and testApplication testing. USE WHEN building Ktor HTTP servers or REST APIs, wiring Koin DI, configuring Auth/CORS/ContentNegotiation plugins, or writing Ktor integration tests.
الانتقال إلى التثبيت سوق المهارات اكتشف واستكشف مهارات الذكاء الاصطناعي التي بناها المجتمع.
المهن ذات الصلة SOC
استنادا إلى تصنيف SOC المهني
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
نسخ Promptعرض تفاصيل Prompt يتجاوز الأمر المباشر Prompt المخصّص للمراجعة. افحص المصدر قبل تشغيله.
npx skills add https://github.com/Sheshiyer/skill-clusters --skill kotlin-ktor-patternsيبقى الأمر في سطر واحد. مرّر أفقيًا لمراجعته كاملًا قبل النسخ.
تفضّل نسخة محلية؟ نزّل الملفات المتاحة حاليًا لدى SkillsMP.
تحميل Zip جاري التحميل... المزيد من هذا المستودع Arcplume runs Grok through the Grok Build CLI's own OAuth-authenticated session (grok login) for image generation, with strict preflight validation, secret-safe handling, and headless CLI-driven execution -- no separate XAI_API_KEY billing. Video falls back to the billed xAI API. USE WHEN a user wants to generate an image via a locally logged-in Grok Build CLI session, e.g. 'generate an image with grok', 'use grok build', or 'use my logged-in grok session'.
Shared reference for the Selemene cluster: the two report surfaces (deterministic Rust reports vs. narrative witness-pipeline readings), the @selemene/bridge CLI contract, the output manifest format, and non-prescriptive witnessing tone. USE WHEN deciding which Selemene surface to invoke or when routing between birth/compatibility/transit reports and solo/dyadic readings.
Route Selemene Engine tasks to the right surface: deterministic reports (birth/compatibility/transit) via selemene-core and the @selemene/bridge CLI, or narrative witness readings via selemene-report. USE WHEN the user wants anything under the Selemene/Noesis umbrella but has not named the exact surface.
name kotlin-ktor-patterns description Ktor server patterns: routing DSL, plugins, authentication, Koin DI, kotlinx.serialization, WebSockets, and testApplication testing. USE WHEN building Ktor HTTP servers or REST APIs, wiring Koin DI, configuring Auth/CORS/ContentNegotiation plugins, or writing Ktor integration tests. cluster jvm version 1.0.0
Ktor Server Patterns
Comprehensive Ktor patterns for building robust, maintainable HTTP servers with Kotlin coroutines.
When to Activate
Building Ktor HTTP servers
Configuring Ktor plugins (Auth, CORS, ContentNegotiation, StatusPages)
Implementing REST APIs with Ktor
Setting up dependency injection with Koin
Writing Ktor integration tests with testApplication
Working with WebSockets in Ktor
Application Structure
Standard Ktor Project Layout
src/main/kotlin/
├── com/example/
│ ├── Application.kt # Entry point, module configuration
│ ├── plugins/
│ │ ├── Routing.kt # Route definitions
│ │ ├── Serialization.kt # Content negotiation setup
│ │ ├── Authentication.kt # Auth configuration
│ │ ├── StatusPages.kt # Error handling
│ │ └── CORS.kt # CORS configuration
│ ├── routes/
│ │ ├── UserRoutes.kt # /users endpoints
│ │ ├── AuthRoutes.kt # /auth endpoints
│ │ └── HealthRoutes.kt # /health endpoints
│ ├── models/
│ │ ├── User.kt # Domain models
│ │ └── ApiResponse.kt # Response envelopes
│ ├── services/
│ │ ├── UserService.kt # Business logic
│ │ └── AuthService.kt # Auth logic
│ ├── repositories/
│ │ ├── UserRepository.kt # Data access interface
│ │ └── ExposedUserRepository.kt
│ └── di/
│ └── AppModule.kt # Koin modules
src/test/kotlin/
├── com/example/
│ ├── routes/
│ │ └── UserRoutesTest.kt
│ └── services/
│ └── UserServiceTest.kt
Application Entry Point
fun main () {
embeddedServer(Netty, port = 8080 , module = Application::module).start(wait = true )
}
fun Application.module () {
configureSerialization()
configureAuthentication()
configureStatusPages()
configureCORS()
configureDI()
configureRouting()
}
Routing DSL
Basic Routes
fun Application.configureRouting () {
routing {
userRoutes()
authRoutes()
healthRoutes()
}
}
fun Route.userRoutes () {
val userService by inject<UserService>()
route("/users" ) {
get {
val users = userService.getAll()
call.respond(users)
}
get ("/{id}" ) {
val id = call.parameters["id" ]
?: return @get call.respond(HttpStatusCode.BadRequest, "Missing id" )
val user = userService.getById(id)
?: return @get call.respond(HttpStatusCode.NotFound)
call.respond(user)
}
post {
val request = call.receive<CreateUserRequest>()
val user = userService.create(request)
call.respond(HttpStatusCode.Created, user)
}
put("/{id}" ) {
val id = call.parameters["id" ]
?: return @put call.respond(HttpStatusCode.BadRequest, "Missing id" )
val request = call.receive<UpdateUserRequest>()
val user = userService.update(id, request)
?: return @put call.respond(HttpStatusCode.NotFound)
call.respond(user)
}
delete("/{id}" ) {
val id = call.parameters["id" ]
?: return @delete call.respond(HttpStatusCode.BadRequest, "Missing id" )
val deleted = userService.delete(id)
if (deleted) call.respond(HttpStatusCode.NoContent)
else call.respond(HttpStatusCode.NotFound)
}
}
}
Route Organization with Authenticated Routes fun Route.userRoutes () {
route("/users" ) {
get { }
get ("/{id}" ) { }
authenticate("jwt" ) {
post { }
put("/{id}" ) { }
delete("/{id}" ) { }
}
}
}
Content Negotiation & Serialization
kotlinx.serialization Setup
fun Application.configureSerialization () {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = false
ignoreUnknownKeys = true
encodeDefaults = true
explicitNulls = false
})
}
}
Serializable Models @Serializable
data class UserResponse (
val id: String,
val name: String,
val email: String,
val role: Role,
@Serializable(with = InstantSerializer::class)
val createdAt: Instant,
)
@Serializable
data class CreateUserRequest (
val name: String,
val email: String,
val role: Role = Role.USER,
)
@Serializable
data class ApiResponse <T >(
val success: Boolean ,
val data : T? = null ,
val error: String? = null ,
) {
companion object {
fun <T> ok (data : T ) : ApiResponse<T> = ApiResponse(success = true , data = data )
fun <T> error (message: String ) : ApiResponse<T> = ApiResponse(success = false , error = message)
}
}
@Serializable
data class PaginatedResponse <T >(
val data : List<T>,
val total: Long ,
val page: Int ,
val limit: Int ,
)
Custom Serializers object InstantSerializer : KSerializer<Instant> {
override val descriptor = PrimitiveSerialDescriptor("Instant" , PrimitiveKind.STRING)
override fun serialize (encoder: Encoder , value: Instant ) =
encoder.encodeString(value.toString())
override fun deserialize (decoder: Decoder ) : Instant =
Instant.parse(decoder.decodeString())
}
Authentication
JWT Authentication
fun Application.configureAuthentication () {
val jwtSecret = environment.config.property("jwt.secret" ).getString()
val jwtIssuer = environment.config.property("jwt.issuer" ).getString()
val jwtAudience = environment.config.property("jwt.audience" ).getString()
val jwtRealm = environment.config.property("jwt.realm" ).getString()
install(Authentication) {
jwt("jwt" ) {
realm = jwtRealm
verifier(
JWT.require(Algorithm.HMAC256(jwtSecret))
.withAudience(jwtAudience)
.withIssuer(jwtIssuer)
.build()
)
validate { credential ->
if (credential.payload.audience.contains(jwtAudience)) {
JWTPrincipal(credential.payload)
} else {
null
}
}
challenge { _, _ ->
call.respond(HttpStatusCode.Unauthorized, ApiResponse.error<Unit >("Invalid or expired token" ))
}
}
}
}
fun ApplicationCall.userId () : String =
principal<JWTPrincipal>()
?.payload
?.getClaim("userId" )
?.asString()
?: throw AuthenticationException("No userId in token" )
Auth Routes fun Route.authRoutes () {
val authService by inject<AuthService>()
route("/auth" ) {
post("/login" ) {
val request = call.receive<LoginRequest>()
val token = authService.login(request.email, request.password)
?: return @post call.respond(
HttpStatusCode.Unauthorized,
ApiResponse.error<Unit >("Invalid credentials" ),
)
call.respond(ApiResponse.ok(TokenResponse(token)))
}
post("/register" ) {
val request = call.receive<RegisterRequest>()
val user = authService.register(request)
call.respond(HttpStatusCode.Created, ApiResponse.ok(user))
}
authenticate("jwt" ) {
get ("/me" ) {
val userId = call.userId()
val user = authService.getProfile(userId)
call.respond(ApiResponse.ok(user))
}
}
}
}
Status Pages (Error Handling)
fun Application.configureStatusPages () {
install(StatusPages) {
exception<ContentTransformationException> { call, cause ->
call.respond(
HttpStatusCode.BadRequest,
ApiResponse.error<Unit >("Invalid request body: ${cause.message} " ),
)
}
exception<IllegalArgumentException> { call, cause ->
call.respond(
HttpStatusCode.BadRequest,
ApiResponse.error<Unit >(cause.message ?: "Bad request" ),
)
}
exception<AuthenticationException> { call, _ ->
call.respond(
HttpStatusCode.Unauthorized,
ApiResponse.error<Unit >("Authentication required" ),
)
}
exception<AuthorizationException> { call, _ ->
call.respond(
HttpStatusCode.Forbidden,
ApiResponse.error<Unit >("Access denied" ),
)
}
exception<NotFoundException> { call, cause ->
call.respond(
HttpStatusCode.NotFound,
ApiResponse.error<Unit >(cause.message ?: "Resource not found" ),
)
}
exception<Throwable> { call, cause ->
call.application.log.error("Unhandled exception" , cause)
call.respond(
HttpStatusCode.InternalServerError,
ApiResponse.error<Unit >("Internal server error" ),
)
}
status(HttpStatusCode.NotFound) { call, status ->
call.respond(status, ApiResponse.error<Unit >("Route not found" ))
}
}
}
CORS Configuration
fun Application.configureCORS () {
install(CORS) {
allowHost("localhost:3000" )
allowHost("example.com" , schemes = listOf("https" ))
allowHeader(HttpHeaders.ContentType)
allowHeader(HttpHeaders.Authorization)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Patch)
allowCredentials = true
maxAgeInSeconds = 3600
}
}
Koin Dependency Injection
Module Definition
val appModule = module {
single<Database> { DatabaseFactory.create(get ()) }
single<UserRepository> { ExposedUserRepository(get ()) }
single<OrderRepository> { ExposedOrderRepository(get ()) }
single { UserService(get ()) }
single { OrderService(get (), get ()) }
single { AuthService(get (), get ()) }
}
fun Application.configureDI () {
install(Koin) {
modules(appModule)
}
}
Using Koin in Routes fun Route.userRoutes () {
val userService by inject<UserService>()
route("/users" ) {
get {
val users = userService.getAll()
call.respond(ApiResponse.ok(users))
}
}
}
Koin for Testing class UserServiceTest : FunSpec (), KoinTest {
override fun extensions () = listOf(KoinExtension(testModule))
private val testModule = module {
single<UserRepository> { mockk() }
single { UserService(get ()) }
}
private val repository by inject<UserRepository>()
private val service by inject<UserService>()
init {
test("getUser returns user" ) {
coEvery { repository.findById("1" ) } returns testUser
service.getById("1" ) shouldBe testUser
}
}
}
Request Validation
fun Route.userRoutes () {
val userService by inject<UserService>()
post("/users" ) {
val request = call.receive<CreateUserRequest>()
require(request.name.isNotBlank()) { "Name is required" }
require(request.name.length <= 100 ) { "Name must be 100 characters or less" }
require(request.email.matches(Regex(".+@.+\\..+" ))) { "Invalid email format" }
val user = userService.create(request)
call.respond(HttpStatusCode.Created, ApiResponse.ok(user))
}
}
fun CreateUserRequest.validate () {
require(name.isNotBlank()) { "Name is required" }
require(name.length <= 100 ) { "Name must be 100 characters or less" }
require(email.matches(Regex(".+@.+\\..+" ))) { "Invalid email format" }
}
WebSockets fun Application.configureWebSockets () {
install(WebSockets) {
pingPeriod = 15. seconds
timeout = 15. seconds
maxFrameSize = 64 * 1024
masking = false
}
}
fun Route.chatRoutes () {
val connections = Collections.synchronizedSet<Connection>(LinkedHashSet())
webSocket("/chat" ) {
val thisConnection = Connection(this )
connections += thisConnection
try {
send("Connected! Users online: ${connections.size} " )
for (frame in incoming) {
frame as ? Frame.Text ?: continue
val text = frame.readText()
val message = ChatMessage(thisConnection.name, text)
val snapshot = synchronized(connections) { connections.toList() }
snapshot.forEach { conn ->
conn.session.send(Json.encodeToString(message))
}
}
} catch (e: Exception) {
logger.error("WebSocket error" , e)
} finally {
connections -= thisConnection
}
}
}
data class Connection (val session: DefaultWebSocketSession) {
val name: String = "User-${counter.getAndIncrement()} "
companion object {
private val counter = AtomicInteger(0 )
}
}
testApplication Testing
Basic Route Testing class UserRoutesTest : FunSpec ({
test("GET /users returns list of users" ) {
testApplication {
application {
install(Koin) { modules(testModule) }
configureSerialization()
configureRouting()
}
val response = client.get ("/users" )
response.status shouldBe HttpStatusCode.OK
val body = response.body<ApiResponse<List<UserResponse>>>()
body.success shouldBe true
body.data .shouldNotBeNull().shouldNotBeEmpty()
}
}
test("POST /users creates a user" ) {
testApplication {
application {
install(Koin) { modules(testModule) }
configureSerialization()
configureStatusPages()
configureRouting()
}
val client = createClient {
install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) {
json()
}
}
val response = client.post("/users" ) {
contentType(ContentType.Application.Json)
setBody(CreateUserRequest("Alice" , "alice@example.com" ))
}
response.status shouldBe HttpStatusCode.Created
}
}
test("GET /users/{id} returns 404 for unknown id" ) {
testApplication {
application {
install(Koin) { modules(testModule) }
configureSerialization()
configureStatusPages()
configureRouting()
}
val response = client.get ("/users/unknown-id" )
response.status shouldBe HttpStatusCode.NotFound
}
}
})
Testing Authenticated Routes class AuthenticatedRoutesTest : FunSpec ({
test("protected route requires JWT" ) {
testApplication {
application {
install(Koin) { modules(testModule) }
configureSerialization()
configureAuthentication()
configureRouting()
}
val response = client.post("/users" ) {
contentType(ContentType.Application.Json)
setBody(CreateUserRequest("Alice" , "alice@example.com" ))
}
response.status shouldBe HttpStatusCode.Unauthorized
}
}
test("protected route succeeds with valid JWT" ) {
testApplication {
application {
install(Koin) { modules(testModule) }
configureSerialization()
configureAuthentication()
configureRouting()
}
val token = generateTestJWT(userId = "test-user" )
val client = createClient {
install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) { json() }
}
val response = client.post("/users" ) {
contentType(ContentType.Application.Json)
bearerAuth(token)
setBody(CreateUserRequest("Alice" , "alice@example.com" ))
}
response.status shouldBe HttpStatusCode.Created
}
}
})
Configuration
application.yaml ktor:
application:
modules:
- com.example.ApplicationKt.module
deployment:
port: 8080
jwt:
secret: ${JWT_SECRET}
issuer: "https://example.com"
audience: "https://example.com/api"
realm: "example"
database:
url: ${DATABASE_URL}
driver: "org.postgresql.Driver"
maxPoolSize: 10
Reading Config fun Application.configureDI () {
val dbUrl = environment.config.property("database.url" ).getString()
val dbDriver = environment.config.property("database.driver" ).getString()
val maxPoolSize = environment.config.property("database.maxPoolSize" ).getString().toInt()
install(Koin) {
modules(module {
single { DatabaseConfig(dbUrl, dbDriver, maxPoolSize) }
single { DatabaseFactory.create(get ()) }
})
}
}
Quick Reference: Ktor Patterns Pattern Description route("/path") { get { } }Route grouping with DSL call.receive<T>()Deserialize request body call.respond(status, body)Send response with status call.parameters["id"]Read path parameters call.request.queryParameters["q"]Read query parameters install(Plugin) { }Install and configure plugin authenticate("name") { }Protect routes with auth by inject<T>()Koin dependency injection testApplication { }Integration testing
Remember : Ktor is designed around Kotlin coroutines and DSLs. Keep routes thin, push logic to services, and use Koin for dependency injection. Test with testApplication for full integration coverage.