Sebelum menulis kode, kita perlu menetapkan kontrak API: endpoint apa yang disediakan, request body seperti apa, response apa yang dikembalikan, dan status code mana yang dipakai untuk setiap kasus. Pada Bagian 1 kita merancang kontrak ini sebagai dokumen yang dapat menjadi rujukan implementasi di Bagian 2 hingga 4.
Kontrak REST API #
Kontrak API terdiri dari lima elemen:
- HTTP method —
GET,POST,PUT,DELETE. - Path — jalur URL beserta parameter.
- Request body — payload JSON yang dikirim klien.
- Response body — payload JSON yang dikembalikan server.
- Status code — kode status HTTP yang menunjukkan hasil.
Lima elemen ini harus konsisten agar klien dapat memprediksi perilaku API. Dokumentasi OpenAPI/Swagger sering dipakai untuk menstandarkan kontrak, tetapi untuk belajar kita memakai deklarasi Kotlin yang dapat dieksekusi sebagai living documentation.
Deklarasi Endpoint #
Kode berikut mendeklarasikan endpoint sebagai data class dan enum:
// Bagian 1: Desain REST API.
// Mendemonstrasikan struktur endpoint, request/response, dan status code
// untuk manajemen tugas, tanpa implementasi server nyata.
data class Tugas(
val id: Int,
val judul: String,
val selesai: Boolean = false
)
data class PermintaanBuat(val judul: String)
data class PermintaanPerbarui(val judul: String? = null, val selesai: Boolean? = null)
data class Respons<T>(val data: T? = null, val galat: String? = null, val status: Int)
enum class HttpStatus(val kode: Int, val label: String) {
OK(200, "OK"),
CREATED(201, "Created"),
NO_CONTENT(204, "No Content"),
BAD_REQUEST(400, "Bad Request"),
NOT_FOUND(404, "Not Found"),
CONFLICT(409, "Conflict"),
UNAUTHORIZED(401, "Unauthorized"),
INTERNAL_ERROR(500, "Internal Server Error")
}
object SkemaEndpoint {
data class Endpoint(
val metode: String,
val jalur: String,
val deskripsi: String,
val statusBerhasil: List<Int>,
val statusGagal: List<Int>
)
val daftar = listOf(
Endpoint("GET", "/tasks", "Daftar semua tugas", listOf(200), listOf(401, 500)),
Endpoint("POST", "/tasks", "Buat tugas baru", listOf(201), listOf(400, 401, 500)),
Endpoint("GET", "/tasks/{id}", "Ambil tugas by id", listOf(200), listOf(404, 401, 500)),
Endpoint("PUT", "/tasks/{id}", "Perbarui tugas by id", listOf(200), listOf(400, 404, 401, 500)),
Endpoint("DELETE", "/tasks/{id}", "Hapus tugas by id", listOf(204), listOf(404, 401, 500)),
Endpoint("POST", "/auth/login", "Login dan dapatkan JWT", listOf(200), listOf(401, 500))
)
}
fun main() {
println("=== Endpoint REST API ===")
println("%-7s %-18s %-25s %s".format("Metode", "Jalur", "Deskripsi", "Status Berhasil"))
SkemaEndpoint.daftar.forEach { e ->
println("%-7s %-18s %-25s %s".format(
e.metode, e.jalur, e.deskripsi, e.statusBerhasil.joinToString(",")
))
}
println()
println("=== Skema request/response ===")
val contohBuat = PermintaanBuat("Belajar Ktor")
println("POST /tasks body: ${contohBuat}")
val respons = Respons(data = Tugas(1, contohBuat.judul), status = HttpStatus.CREATED.kode)
println("Response 201: ${respons}")
println()
println("=== Status code umum ===")
HttpStatus.values().forEach { println("${it.kode} ${it.label}") }
println()
println("=== Skenario galat ===")
val gagalValidasi = Respons<Tugas>(galat = "Judul tidak boleh kosong", status = HttpStatus.BAD_REQUEST.kode)
println("400: ${gagalValidasi.galat}")
val tidakAda = Respons<Tugas>(galat = "Tugas id=99 tidak ditemukan", status = HttpStatus.NOT_FOUND.kode)
println("404: ${tidakAda.galat}")
}Output yang Dihasilkan:
=== Endpoint REST API ===
Metode Jalur Deskripsi Status Berhasil
GET /tasks Daftar semua tugas 200
POST /tasks Buat tugas baru 201
GET /tasks/{id} Ambil tugas by id 200
PUT /tasks/{id} Perbarui tugas by id 200
DELETE /tasks/{id} Hapus tugas by id 204
POST /auth/login Login dan dapatkan JWT 200
=== Skema request/response ===
POST /tasks body: PermintaanBuat(judul=Belajar Ktor)
Response 201: Respons(data=Tugas(id=1, judul=Belajar Ktor, selesai=false), galat=null, status=201)
=== Status code umum ===
200 OK
201 Created
204 No Content
400 Bad Request
404 Not Found
409 Conflict
401 Unauthorized
500 Internal Server Error
=== Skenario galat ===
400: Judul tidak boleh kosong
404: Tugas id=99 tidak ditemukanMari kita bedah elemen desain:
data class PermintaanBuat(val judul: String): request body untukPOST /tasks. Hanya satu field yang wajib:judul.data class PermintaanPerbarui(val judul: String? = null, val selesai: Boolean? = null): request body untukPUT /tasks/{id}. Semua field nullable dengan defaultnull, sehingga klien dapat memperbarui sebagian (partial update).data class Respons<T>(val data: T?, val galat: String?, val status: Int): respons generik.databerisi payload sukses,galatberisi pesan galat,statusmenunjukkan kode HTTP. Hanya satu daridata/galatyang terisi dalam praktik.enum class HttpStatus(val kode: Int, val label: String): setiap konstanta membawa dua property. AksesHttpStatus.CREATED.kodemengembalikan201.object SkemaEndpoint: tunggal, menyimpan daftar endpoint. PropertydaftarberupaList<Endpoint>yang immutable."GET" to "/tasks": bentukPair<String, String>untuk memetakan metode dan jalur. Dipakai di Bagian 2 untuk routing.
Pemilihan Status Code #
Status code HTTP dibagi lima rentang:
| Rentang | Arti | Contoh |
|---|---|---|
| 1xx | Informational | Jarang dipakai |
| 2xx | Sukses | 200 OK, 201 Created, 204 No Content |
| 3xx | Redirection | 301 Moved, 304 Not Modified |
| 4xx | Galat klien | 400 Bad Request, 404 Not Found, 401 Unauthorized |
| 5xx | Galat server | 500 Internal Error, 503 Service Unavailable |
Aturan pemilihan untuk proyek ini:
POSTsukses →201 Created(sumber daya baru dibuat).GET,PUTsukses →200 OK.DELETEsukses →204 No Content(tidak ada body).- Validasi gagal →
400 Bad Request. - Sumber daya tidak ditemukan →
404 Not Found. - Tidak terotentikasi →
401 Unauthorized.
Idempotency #
Empat method HTTP memiliki sifat idempotensi yang berbeda:
GET— idempotent dan safe (tidak mengubah state).PUT— idempotent (memanggil dua kali memberi hasil yang sama).DELETE— idempotent (menghapus dua kali: pertama hapus, kedua tidak ada efek).POST— non-idempotent (memanggil dua kali membuat dua sumber daya).
Memahami sifat ini penting untuk retry otomatis dan caching. Klien dapat mengulang PUT atau DELETE tanpa takut duplikat, tetapi harus berhati-hati dengan POST.
Referensi Lanjutan #
- Ktor routing — panduan routing Ktor untuk merancang endpoint REST manajemen tugas
Ringkasan Bagian 1 #
Kontrak API kini terdefinisi: enam endpoint dengan method, path, dan status code yang konsisten. Request dan response body dideklarasikan sebagai data class, dan HttpStatus menjadi sumber tunggal kebenaran untuk kode status. Pada Bagian 2 kita mengimplementasikan routing yang melayani endpoint ini.