kotlinintermediate

Ktor HTTP Client — GET, POST, and Auth

Make HTTP requests with Ktor client: GET, POST, headers, auth, retries, and JSON serialization.

kotlin
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.*

@Serializable
data class Post(val id: Int, val title: String, val body: String, val userId: Int)

@Serializable
data class CreatePost(val title: String, val body: String, val userId: Int)

class ApiClient : AutoCloseable {
    private val client = HttpClient(CIO) {
        // JSON serialization
        install(ContentNegotiation) {
            json(kotlinx.serialization.json.Json {
                ignoreUnknownKeys = true
                prettyPrint = true
            })
        }

        // Logging
        install(Logging) {
            level = LogLevel.INFO
        }

        // Defaults
        defaultRequest {
            url("https://jsonplaceholder.typicode.com")
            contentType(ContentType.Application.Json)
        }

        // Timeout
        install(HttpTimeout) {
            requestTimeoutMillis = 10_000
            connectTimeoutMillis = 5_000
        }

        // Retry on failure
        install(HttpRequestRetry) {
            retryOnServerErrors(maxRetries = 3)
            exponentialDelay()
        }
    }

    // GET single
    suspend fun getPost(id: Int): Post =
        client.get("/posts/$id").body()

    // GET list
    suspend fun getPosts(limit: Int = 10): List<Post> =
        client.get("/posts") {
            parameter("_limit", limit)
        }.body()

    // POST
    suspend fun createPost(post: CreatePost): Post =
        client.post("/posts") {
            setBody(post)
        }.body()

    // PUT
    suspend fun updatePost(id: Int, post: CreatePost): Post =
        client.put("/posts/$id") {
            setBody(post)
        }.body()

    // DELETE
    suspend fun deletePost(id: Int): HttpStatusCode =
        client.delete("/posts/$id").status

    // Auth header
    suspend fun getProtected(token: String): String =
        client.get("/posts/1") {
            bearerAuth(token)
        }.bodyAsText()

    // Raw response
    suspend fun getRaw(url: String): Pair<HttpStatusCode, String> {
        val response: HttpResponse = client.get(url)
        return response.status to response.bodyAsText()
    }

    override fun close() = client.close()
}

suspend fun main() {
    ApiClient().use { api ->
        // GET
        val post = api.getPost(1)
        println("Post: ${post.title}")

        // GET list
        val posts = api.getPosts(3)
        posts.forEach { println("  ${it.id}: ${it.title}") }

        // POST
        val created = api.createPost(
            CreatePost("New Post", "Content here", 1)
        )
        println("Created: ${created.id} - ${created.title}")

        // DELETE
        val status = api.deletePost(1)
        println("Delete status: $status")
    }
}

Use Cases

  • REST API consumption in Kotlin applications
  • Microservice-to-microservice communication
  • HTTP client with retry and timeout handling

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.