AgentSkillsCN

ktor-backend

当您撰写产品需求、界定范围,以及规划功能时,可使用此技能。适用于 PRD 编制、验收标准,以及 MVP 规划。

SKILL.md
--- frontmatter
name: ktor-backend
description: This skill should be used when creating Ktor server endpoints, BFF APIs, and backend services. Use for REST APIs, request validation, and server features.

When to Use

Use this skill when:

  • Creating new Ktor server endpoints and routes
  • Implementing REST APIs and BFF (Backend-for-Frontend) services
  • Adding request validation with kotlinx.serialization
  • Designing API versioning strategies
  • Writing TestApplication integration tests
  • Setting up OpenAPI/Swagger documentation
  • Implementing authentication and authorization middleware
  • Adding server features like CORS, compression, logging

Do NOT use this skill for:

  • Client-side HTTP requests (use KMP Mobile Expert for HttpClient setup)
  • Compose UI implementation (use compose-screen skill)
  • Shared business logic (use kmp-mobile-expert skill)
  • Database schema design (this project uses PokéAPI, no database layer yet)

Essential Workflows

Workflow 1: Create New REST Endpoint

To add a new Ktor endpoint following API conventions:

  1. Define request/response models with @Serializable:

    kotlin
    @Serializable
    data class PokemonListRequest(val limit: Int, val offset: Int)
    
    sealed interface PokemonListResponse {
        @Serializable
        data class Success(
            val pokemons: List<Pokemon>,
            val count: Int,
            val next: String?
        ) : PokemonListResponse
    
        @Serializable
        data class Error(val message: String, val code: Int) : PokemonListResponse
    }
    
  2. Create route in routing block with proper grouping:

    kotlin
    fun Application.module() {
        routing {
            route("/api/v1") {
                route("/pokemon") {
                    get {
                        val request = call.receive<PokemonListRequest>()
                        val response = getPokemonList(request)
                        call.respond(HttpStatusCode.OK, response)
                    }
    
                    get("/{id}") {
                        val id = call.parameters["id"]?.toIntOrNull()
                            ?: return@get call.respond(HttpStatusCode.BadRequest, PokemonListResponse.Error("Invalid ID", 400))
                        val response = getPokemonById(id)
                        call.respond(HttpStatusCode.OK, response)
                    }
            }
        }
    }
    
  3. Add request validation before processing:

    kotlin
    fun Application.configureValidation() {
        install(ContentNegotiation) {
            json(Json {
                ignoreUnknownKeys = true
                prettyPrint = true
            })
        }
    }
    
  4. Write TestApplication integration test:

    kotlin
    class PokemonRouteTest : FunSpec({
        test("GET /api/v1/pokemon returns list") {
            withTestApplication({
                configureRouting()
                configureSerialization()
            }) {
                with(handleRequest(HttpMethod.Get, "/api/v1/pokemon?limit=20&offset=0")) {
                    response.status() shouldBe HttpStatusCode.OK
                    val content = response.content!!
                    content shouldContain "\"pokemons\":"
                }
            }
        }
    })
    
  5. Validate endpoint with script:

    bash
    .claude/skills/ktor-backend/scripts/validate-endpoint.sh server/src/main/kotlin/com/minddistrict/multiplatformpoc/routes/PokemonRoutes.kt
    

Workflow 2: Add API Versioning

To implement API versioning strategy:

  1. Route by version prefix:

    kotlin
    routing {
        // v1 API - stable
        route("/api/v1") {
            pokemonRoutes()  // Existing stable endpoints
        }
    
        // v2 API - beta/new features
        route("/api/v2") {
            pokemonRoutesV2()  // New endpoints with breaking changes
        }
    }
    
  2. Use sealed classes for version-specific responses:

    kotlin
    // v1 response (stable)
    @Serializable
    data class PokemonResponseV1(val name: String, val url: String)
    
    // v2 response (with additional fields)
    @Serializable
    data class PokemonResponseV2(
        val id: Int,
        val name: String,
        val types: List<String>,
        val sprites: Sprites
    )
    
  3. Deprecate old versions with headers:

    kotlin
    route("/api/v1") {
        intercept(ApplicationCallPipeline.Call) {
            call.response.headers.append("X-API-Deprecated", "true")
            call.response.headers.append("X-API-Version", "v2 available at /api/v2")
            proceed()
        }
    }
    

Workflow 3: Set Up OpenAPI Documentation

To add Swagger/OpenAPI documentation:

  1. Add Ktor Swagger plugin to dependencies:

    kotlin
    // server/build.gradle.kts
    dependencies {
        implementation(libs.ktor.swagger)
    }
    
  2. Configure Swagger plugin:

    kotlin
    fun Application.configureSwagger() {
        install(Swagger) {
            swagger {
                info {
                    title = "Pokédex API"
                    version = "1.0.0"
                    description = "Kotlin Multiplatform Pokédex Backend-for-Frontend API"
                }
                schemes listOf(Scheme.HTTP, Scheme.HTTPS)
            }
        }
    }
    
    routing {
        swaggerUI(path = "swagger", swaggerFile = "openapi.json")
    }
    
  3. Access documentation at http://localhost:8080/swagger

Critical Guardrails

RuleWhy It MattersEnforcement
Always use route() blocks for groupingProvides logical API structure and path hierarchyValidation script checks for route( presence
Never mix HTTP methods in same routeViolates REST principles, causes routing conflictsUse separate get, post, put, delete blocks
Always validate path parametersPrevents NPE and malformed requestsUse toIntOrNull() with error responses
Always respond with appropriate status codesAPI contracts depend on proper HTTP semanticsUse HttpStatusCode.OK, BadRequest, NotFound, etc.
Always write TestApplication testsEnsures endpoints work before deploymentRequired for all new routes
Never expose internal exceptionsSecurity risk, leaks implementation detailsUse sealed response types with error messages

Quick Reference

Validation Commands

CommandPurposeWhen to Run
./gradlew :server:runRun Ktor server locallyDuring development
./gradlew :server:testRun server integration testsAfter adding endpoints
.claude/skills/ktor-backend/scripts/validate-endpoint.sh <file>Validate endpoint conventionsAfter creating routes
bash -n <script>Check shell script syntaxBefore committing scripts

Ktor Routing Patterns

PatternExampleUse Case
Simple GETget("/") { call.respondText("Hello") }Basic endpoints
Path parameterget("/{id}") { val id = call.parameters["id"] }Resource lookup
Query parameterget("/search") { val q = call.request.queryParameters["q"] }Search/filter
Nested routesroute("/api/v1") { route("/pokemon") { } }API versioning
POST with bodypost { val body = call.receive<Request>() }Create resources

Request/Response Templates

GET endpoint:

kotlin
get("/pokemon/{id}") {
    val id = call.parameters["id"]?.toIntOrNull()
        ?: return@get call.respond(
            HttpStatusCode.BadRequest,
            ErrorResponse(message = "Invalid ID", code = 400)
        )

    val pokemon = repository.getPokemonById(id)
    call.respond(HttpStatusCode.OK, pokemon)
}

POST endpoint:

kotlin
post("/pokemon") {
    val request = try {
        call.receive<CreatePokemonRequest>()
    } catch (e: Exception) {
        return@post call.respond(
            HttpStatusCode.BadRequest,
            ErrorResponse(message = "Invalid request body", code = 400)
        )
    }

    val pokemon = repository.createPokemon(request)
    call.respond(HttpStatusCode.Created, pokemon)
}

Error response:

kotlin
@Serializable
data class ErrorResponse(
    val message: String,
    val code: Int,
    val details: Map<String, String>? = null
)

Cross-References

DocumentPurposeLink
Architecture + conventionsMaster reference for architecture and patternsconventions.md
Critical patterns6 core patterns including Either boundarycritical_patterns_quick_ref.md
Testing strategyKotest, MockK, Turbine for integration teststesting_strategy.md
Ktor documentationOfficial Ktor server documentationhttps://ktor.io/docs/
Version catalogDependency versions for Ktor pluginslibs.versions.toml

Reference Implementation

Current server setup:

Examples to reference:

  • Ktor routing: Application.kt shows basic route() and get() usage
  • Serialization: Add ContentNegotiation plugin with kotlinx.serialization
  • Testing: Use TestApplication for integration tests

Recommended Ktor Plugins

PluginPurposeVersion Catalog Key
ContentNegotiationJSON serialization/deserializationktor.serverContentNegotiation
Serializationkotlinx.serialization supportktor.serializationJson
StatusPagesGlobal error handlingktor.serverStatusPages
CallLoggingRequest/response loggingktor.serverCallLogging
CORSCross-Origin Resource Sharingktor.serverCors
CompressionGzip compressionktor.serverCompression
SwaggerOpenAPI documentationktor.swagger