Kata Pengantar
Setiap kali Anda membuka aplikasi — memesan makanan, membayar tagihan, atau sekadar menyukai sebuah foto — ada percakapan diam-diam yang terjadi di balik layar. Frontend berbisik kepada backend: "tolong berikan data ini," "simpan yang ini," "apakah pengguna ini boleh?" Percakapan itu punya tata bahasa, aturan, dan etiket. Tata bahasa itulah yang kita sebut API — Application Programming Interface.
Kebanyakan bug yang menyakitkan di produksi bukan lahir dari algoritma yang rumit, melainkan dari kontrak yang samar: field yang kadang ada kadang tidak, status code yang dipakai asal, error yang tak bisa dibaca mesin, atau autentikasi yang bocor karena satu header terlewat. Desain API yang baik menutup celah-celah itu sebelum kode ditulis.
Buku ini ditulis untuk semua level. Jika Anda baru mulai, mulailah dari Bab 1–4 secara berurutan; setiap istilah dijelaskan saat pertama muncul. Jika Anda sudah berpengalaman, gunakan daftar isi sebagai peta — lompati ke tRPC, GraphQL, webhooks, atau rate limiting sesuai kebutuhan. Semua contoh kode dirancang untuk bisa disalin dan langsung dijalankan, dan di Bab 16 tersedia Endpoint Designer interaktif yang mengubah deskripsi endpoint Anda menjadi perintah curl, bentuk request/response, dan cuplikan OpenAPI.
Antarmuka yang seragam adalah fitur pusat yang membedakan gaya arsitektur REST dari gaya berbasis jaringan lainnya.
Roy Fielding, "Architectural Styles and the Design of Network-based Software Architectures" (2000)
Cara Membaca Buku Ini
Buku ini terbagi menjadi lima bagian. Bagian I membangun fondasi: mengapa desain penting, cara kerja HTTP, prinsip REST, dan bagaimana menyusun kontrak request/response. Bagian II membahas keamanan dan ketahanan: autentikasi serta penanganan error. Bagian III memperkenalkan paradigma alternatif — tRPC, GraphQL, dan webhooks. Bagian IV menyiapkan API untuk skala: rate limiting, caching, idempotensi, realtime, dokumentasi otomatis, arsitektur BFF, pengujian, dan keamanan ringkas. Bagian V menutup dengan contoh nyata sebuah SaaS, panduan agar API terbaca oleh AI, serta FAQ, glosarium, dan checklist rilis.
Konvensi visual yang dipakai:
- Kata bertanda
seperti iniadalah kode, nama field, header, atau nilai literal. - Badge metode HTTP muncul sebagai GET POST PUT PATCH DELETE.
- Kotak Tip, Perhatian, Bahaya, dan Catatan menyorot hal penting.
- Blok kode diberi label kecil di atasnya agar jelas ini file apa atau perintah apa.
Sepanjang buku, api.app.dev dipakai sebagai host contoh, dan prefiks versi /v1 selalu ditulis eksplisit. Ganti dengan domain Anda sendiri saat mempraktikkannya.
Daftar Istilah Cepat
| Istilah | Arti singkat |
|---|---|
| Endpoint | Satu alamat (URL + metode) yang menerima request, mis. POST /v1/orders. |
| Resource | "Benda" yang diwakili API — order, user, invoice — biasanya kata benda jamak di URL. |
| Payload / Body | Data yang dikirim dalam request atau dikembalikan dalam response, umumnya JSON. |
| Header | Metadata request/response: tipe konten, token, cache, dsb. |
| Status code | Angka 3 digit yang menyatakan hasil: 2xx sukses, 4xx salah klien, 5xx salah server. |
| Idempoten | Operasi yang, diulang berkali-kali, memberi efek sama seperti sekali. |
| Kontrak | Kesepakatan bentuk data dan perilaku antara pemanggil dan penyedia API. |
| Schema | Deskripsi formal struktur data: field, tipe, wajib/opsional, batasan. |
| Token | Kredensial yang membuktikan identitas/izin pemanggil. |
| Webhook | API terbalik: server Anda memanggil URL pihak lain saat suatu peristiwa terjadi. |
Bagian I — Fondasi
Pendahuluan: Mengapa Desain API Penting
design > codeBayangkan dua tim yang membangun jembatan dari dua sisi sungai. Jika mereka tidak menyepakati lebih dulu di mana kedua ujung akan bertemu, seberapa tinggi, dan berapa lebarnya, mereka akan membangun dua jembatan yang tidak pernah menyatu. Frontend dan backend adalah dua tim itu; API adalah titik temu yang disepakati. Desain API adalah pekerjaan menggambar titik temu itu — sebelum beton dituang.
API sebagai kontrak, bukan sekadar kode
Kata kunci yang akan terus muncul di buku ini adalah kontrak. Ketika frontend memanggil GET /v1/orders/ord_7Fq2, ia berharap mendapat sebuah objek order dengan field tertentu, tipe tertentu, dan status code tertentu. Kontrak inilah yang memungkinkan dua tim bekerja paralel: selama bentuknya disepakati, backend bisa mengganti database dan frontend bisa mengganti framework tanpa saling merusak.
Kontrak yang buruk menular. Satu field yang namanya userId di satu endpoint tetapi user_id di endpoint lain memaksa setiap konsumen menulis kode penyesuaian. Satu error yang kadang berupa string dan kadang berupa objek memaksa setiap pemanggil menebak. Biaya ketidakonsistenan tidak dibayar sekali; ia dibayar berulang kali oleh setiap orang yang menyentuh API itu selama bertahun-tahun.
Aturan emas: bentuk dulu, implementasi kemudian. Tuliskan contoh request dan response — bahkan hanya sebagai JSON di dokumen — sebelum menulis satu baris handler. Jika Anda kesulitan menuliskan contohnya, itu pertanda desainnya belum matang.
Siapa saja konsumen API Anda
Mudah mengira konsumen API hanyalah "frontend kita sendiri". Kenyataannya lebih luas:
- Web frontend — SPA atau app server-rendered yang memanggil lewat browser.
- Aplikasi mobile — sering tertinggal versi; API lama harus tetap hidup.
- Integrasi pihak ketiga — mitra yang membangun di atas API Anda; mereka butuh dokumentasi dan stabilitas.
- Layanan internal lain — microservice yang saling memanggil.
- Skrip & otomasi — cron job, pipeline data, dan kini agen AI yang membaca dokumentasi lalu memanggil endpoint Anda sendiri.
Setiap konsumen ini punya toleransi berbeda terhadap perubahan. Frontend sendiri bisa dirilis bersamaan dengan backend; aplikasi mobile lama tidak. Karena itulah versioning dan kompatibilitas mundur (dibahas di Bab 3) bukan kemewahan, melainkan konsekuensi dari memiliki banyak konsumen.
Tiga sifat API yang baik
Sepanjang buku ini, kita akan menilai keputusan desain dengan tiga pertanyaan sederhana:
- Dapat diprediksi. Jika saya tahu satu endpoint, bisakah saya menebak yang lain? Penamaan, format, dan pola error yang konsisten membuat API terasa "seperti satu orang yang menulisnya".
- Sulit dipakai salah. Default yang aman, validasi yang jelas, dan pesan error yang menuntun. API yang baik membuat jalan yang benar menjadi jalan termudah.
- Jujur. Status code mencerminkan kenyataan, dokumentasi cocok dengan perilaku, dan tidak ada "kejutan" tersembunyi. Kepercayaan dibangun dari kejujuran teknis.
Bersikaplah konservatif dalam apa yang Anda kirim, dan liberal dalam apa yang Anda terima.
Jon Postel, RFC 761 (1980) — dikenal sebagai Postel's Law / Prinsip Ketahanan
Prinsip Postel adalah kompas yang berguna, tetapi juga pisau bermata dua. Menerima input yang "kira-kira benar" memang membuat integrasi awal mulus, namun bila berlebihan, ia menyembunyikan bug dan menumpuk perilaku tak terdefinisi. Sikap sehat modern: ketat pada apa yang Anda hasilkan, dan validasi tegas apa yang Anda terima — tetapi berikan pesan error yang ramah. Kita akan kembali ke keseimbangan ini di Bab 4 dan 6.
Apa yang akan Anda bisa lakukan setelah membaca
Di akhir buku, Anda diharapkan mampu: mendesain sekumpulan endpoint REST yang konsisten; memilih di antara REST, tRPC, dan GraphQL secara sadar; merancang skema request/response dengan validasi; mengamankan API dengan token; menangani error secara seragam; membangun dan menerima webhook yang aman; menerapkan rate limiting, caching, dan idempotensi; menyediakan realtime; menulis spesifikasi OpenAPI; serta menguji semuanya. Mari mulai dari lapisan paling dasar: HTTP.
Fundamental HTTP
method · status · header · idempotensiHampir semua API yang akan Anda desain berjalan di atas HTTP. Memahami HTTP dengan baik ibarat seorang penulis memahami tata bahasa: Anda tidak perlu menghafal setiap aturan, tetapi Anda harus tahu di mana kalimat menjadi ambigu. Bab ini merangkum bagian HTTP yang paling sering menentukan kualitas sebuah API.
Anatomi satu request
Sebuah request HTTP terdiri dari empat bagian: metode (verb), path (dan query), header, serta body opsional. Response punya status code, header, dan body. Contoh mentahnya:
HTTP request (mentah)POST /v1/orders?notify=true HTTP/1.1
Host: api.app.dev
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
Idempotency-Key: 9f1c-4a2e-...
{ "productId": "sku_42", "qty": 2 }
HTTP response (mentah)
HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/orders/ord_7Fq2
{ "id": "ord_7Fq2", "status": "confirmed" }
Metode (HTTP verbs)
Metode menyatakan maksud operasi. Memakainya secara benar membuat API dapat diprediksi dan memungkinkan infrastruktur (cache, proxy) berperilaku benar.
| Metode | Maksud | Aman? | Idempoten? |
|---|---|---|---|
| GET | Membaca resource; tidak mengubah apa pun. | Ya | Ya |
| POST | Membuat resource baru atau memicu proses. | Tidak | Tidak* |
| PUT | Mengganti resource secara utuh pada URL tertentu. | Tidak | Ya |
| PATCH | Mengubah sebagian field resource. | Tidak | Tidak* |
| DELETE | Menghapus resource. | Tidak | Ya |
*Bisa dibuat idempoten dengan Idempotency-Key — lihat Bab 10.
Dua istilah penting: aman (safe) berarti operasi tidak mengubah state di server, sehingga boleh di-cache dan diulang bebas. Idempoten berarti mengulang operasi memberi hasil akhir yang sama. DELETE menghapus sekali; memanggilnya lagi tetap menghasilkan "resource itu tidak ada" — efek akhirnya sama, jadi idempoten. Sebaliknya, dua POST pembuatan order tanpa pengaman bisa menghasilkan dua order — itulah kenapa POST tidak idempoten secara default.
Jangan pernah memakai GET untuk operasi yang mengubah data. Browser, prefetcher, dan crawler bebas memanggil GET kapan saja. Sebuah "tombol hapus" yang memanggil GET /delete?id=5 adalah bug yang menunggu terjadi.
Status code
Status code adalah cara tercepat sebuah API memberi tahu apa yang terjadi. Kelompoknya:
- 2xx — Sukses.
200 OK(berhasil, ada body),201 Created(resource baru dibuat; sertakan headerLocation),202 Accepted(diterima, diproses asinkron),204 No Content(berhasil, tanpa body — cocok untuk DELETE). - 3xx — Redireksi.
301/308pindah permanen,304 Not Modified(dipakai dengan ETag — Bab 10). - 4xx — Kesalahan klien.
400(request cacat),401(belum terautentikasi),403(terautentikasi tetapi tidak berizin),404(tidak ditemukan),409(konflik state),422(validasi gagal),429(terlalu banyak request). - 5xx — Kesalahan server.
500(galat tak terduga),502/503(gateway/tidak tersedia),504(timeout hulu).
Perbedaan 401 vs 403 sering keliru. 401 berarti "saya tidak tahu siapa Anda" — kredensial hilang atau tidak valid; klien harus login ulang. 403 berarti "saya tahu siapa Anda, tetapi Anda tidak boleh" — login ulang tidak akan membantu. Perbedaan 400 vs 422: banyak tim memakai 400 untuk JSON yang tidak bisa di-parse dan 422 untuk JSON valid yang melanggar aturan bisnis (mis. qty negatif).
Pilih himpunan status code yang sempit dan konsisten, lalu dokumentasikan artinya. API yang memakai lima status code dengan benar lebih mudah dipakai daripada API yang memakai dua puluh secara sembarangan.
Header yang sering dipakai
| Header | Arah | Guna |
|---|---|---|
Content-Type | keduanya | Format body, mis. application/json. |
Accept | request | Format yang diinginkan klien. |
Authorization | request | Kredensial, mis. Bearer <token>. |
Location | response | URL resource baru (pada 201). |
ETag / If-None-Match | keduanya | Caching & concurrency (Bab 10). |
Idempotency-Key | request | Membuat POST aman diulang (Bab 10). |
Retry-After | response | Kapan boleh coba lagi (pada 429/503). |
X-Request-Id | keduanya | Menelusuri satu request lintas log. |
Idempotensi: mengapa ini jantung keandalan
Jaringan tidak dapat diandalkan. Klien mengirim POST, server memproses dan membuat order, tetapi response hilang di tengah jalan. Klien tidak tahu apakah berhasil, lalu mencoba lagi. Tanpa pengaman, terciptalah dua order. Idempotensi adalah jawaban atas dunia yang tidak sempurna ini: dengan mendesain operasi agar aman diulang, retry menjadi murah dan sistem menjadi tahan gangguan. Kita akan membangun mekanismenya secara konkret di Bab 10; untuk sekarang, ingatlah bahwa setiap kali Anda mendesain operasi tulis, tanyakan: apa yang terjadi jika ini dipanggil dua kali?
Web adalah satu-satunya sistem terdistribusi berskala planet yang pernah kita bangun yang benar-benar berhasil. Kendala-kendala HTTP-lah yang membuatnya mungkin.
Roy Fielding, dalam berbagai kuliah tentang REST
Desain REST
resource · versioning · paginationREST — Representational State Transfer — bukan protokol dan bukan pustaka. Ia adalah sekumpulan kendala arsitektural yang dirumuskan Roy Fielding, yang bila dipatuhi menghasilkan API yang seragam dan dapat diskalakan. Dalam praktik sehari-hari, "REST" sering berarti gaya: memodelkan resource sebagai URL, memakai metode HTTP sesuai maksudnya, dan mengembalikan representasi dalam JSON. Bab ini adalah panduan praktis gaya tersebut.
Resource dan penamaan URL
Blok bangunan REST adalah resource: kata benda yang mewakili sesuatu — user, order, invoice. Aturan penamaan yang sehat:
- Gunakan kata benda jamak:
/orders, bukan/getOrderatau/order. - Biarkan metode yang menyatakan kata kerja: GET
/ordersuntuk membaca, POST/ordersuntuk membuat. - Identitas di path, penyaring di query:
/orders/ord_7Fq2untuk satu item;/orders?status=paiduntuk daftar tersaring. - Nested untuk relasi milik:
/orders/ord_7Fq2/items. Hindari nesting terlalu dalam (maksimal ~2 level); untuk relasi longgar, pakai query filter. - Gunakan
kebab-caseuntuk path multi-kata:/payment-methods.
GET /v1/orders # daftar (paginated)
POST /v1/orders # buat baru -> 201 + Location
GET /v1/orders/{id} # baca satu
PATCH /v1/orders/{id} # ubah sebagian
PUT /v1/orders/{id} # ganti utuh
DELETE /v1/orders/{id} # hapus -> 204
GET /v1/orders/{id}/items # sub-resource
Kata kerja di URL adalah bau desain (code smell). Jika Anda merasa butuh /orders/{id}/cancel, pertimbangkan dua opsi: (a) memodelkannya sebagai perubahan state via PATCH { "status": "cancelled" }, atau (b) memodelkan pembatalan sebagai resource sendiri: POST /orders/{id}/cancellations. Keduanya lebih konsisten daripada endpoint kata kerja lepas.
Versioning
Begitu API Anda punya konsumen yang tak bisa Anda rilis ulang (mobile, mitra), Anda tidak bisa lagi mengubah kontrak seenaknya. Versioning memberi ruang untuk berevolusi tanpa merusak. Tiga gaya umum:
| Gaya | Contoh | Catatan |
|---|---|---|
| URL path | /v1/orders | Paling eksplisit & mudah di-cache; paling populer. |
| Header | Accept: application/vnd.app.v1+json | URL tetap bersih; lebih sulit dites manual. |
| Query | /orders?version=1 | Sederhana; rawan terlupa & bercampur dengan filter. |
Rekomendasi praktis: mulai dengan versi di path (/v1). Perlakukan perubahan yang merusak (menghapus field, mengubah tipe, mengetatkan validasi) sebagai pemicu versi baru. Sebaliknya, menambah field opsional atau endpoint baru bersifat kompatibel-mundur dan tidak butuh versi baru — asalkan konsumen Anda mengabaikan field yang tak dikenal (kembali ke Postel's Law).
Naikkan versi sesedikit mungkin. Setiap versi hidup adalah beban pemeliharaan. Banyak API besar bertahan di v1 selama bertahun-tahun karena disiplin menambah, bukan mengubah.
Pagination
Daftar tak boleh mengembalikan seluruh tabel. Dua pola dominan:
Offset / page
GET /v1/orders?page=2&per_page=20. Mudah dipahami dan mendukung "lompat ke halaman 5". Kelemahannya: pada data yang sering berubah, item bisa terlewat atau berulang (karena offset bergeser), dan makin dalam offset makin lambat kueri database.
Cursor / keyset
GET /v1/orders?limit=20&cursor=eyJpZCI6.... Server mengembalikan next_cursor yang menunjuk posisi setelah item terakhir. Stabil terhadap perubahan dan cepat pada data besar. Kelemahannya: tidak bisa lompat ke halaman sembarang. Ini pilihan default untuk feed dan data berskala besar.
{
"data": [ { "id": "ord_91" }, { "id": "ord_90" } ],
"page": {
"limit": 20,
"next_cursor": "eyJpZCI6Im9yZF84OSJ9",
"has_more": true
}
}
Filter, sort, dan sparse fields
Konvensi query yang konsisten membuat daftar terasa satu keluarga:
- Filter:
?status=paid&min_total=100000. Untuk operator, banyak API memakai gaya?created_at[gte]=2026-01-01. - Sort:
?sort=-created_at,total. Awalan-berarti menurun. Batasi field yang boleh di-sort agar tidak membebani DB. - Sparse fields:
?fields=id,status,totalmemungkinkan klien meminta subset field — menghemat bandwidth mobile. - Pencarian:
?q=nasi+gorenguntuk teks bebas, terpisah dari filter terstruktur.
Jangan pernah membangun kueri SQL dengan menyambung string dari parameter filter/sort mentah. Itu pintu masuk injeksi. Petakan nama field dan operator ke daftar-putih (allowlist) yang Anda kontrol. Lihat Bab 15.
HATEOAS secara ringkas
Huruf terakhir dalam prinsip REST Fielding adalah hypermedia as the engine of application state — HATEOAS. Idenya: response tidak hanya berisi data, tetapi juga tautan ke aksi berikutnya yang mungkin, sehingga klien menavigasi API seperti manusia menavigasi web: mengikuti link, bukan menyusun URL sendiri.
Response dengan tautan hypermedia{
"id": "ord_7Fq2",
"status": "confirmed",
"_links": {
"self": { "href": "/v1/orders/ord_7Fq2" },
"items": { "href": "/v1/orders/ord_7Fq2/items" },
"cancel": { "href": "/v1/orders/ord_7Fq2/cancellations", "method": "POST" }
}
}
Dalam praktik, HATEOAS penuh jarang diterapkan karena klien modern (terutama SPA) cenderung menyusun URL sendiri dan HATEOAS menambah kompleksitas. Namun gagasan-nya tetap berharga: menyertakan tautan relasi dan aksi kondisional (mis. hanya menampilkan link cancel jika order memang bisa dibatalkan) membuat klien lebih tahan terhadap perubahan URL. Ambil semangatnya, terapkan seperlunya.
REST dimaksudkan untuk API yang berumur puluhan tahun. Setiap detail dirancang untuk kelanggengan dan evolusi independen. Banyak yang menyebut sesuatu 'REST' padahal yang mereka maksud hanyalah HTTP+JSON.
Roy Fielding, "REST APIs must be hypertext-driven" (2008)
Kontrak Request/Response
schema · validasi · error konsistenJika Bab 3 tentang di mana data berada, bab ini tentang bentuk data itu. Kontrak request/response adalah spesifikasi tepat: field apa yang masuk, field apa yang keluar, tipe masing-masing, mana yang wajib, dan seperti apa error terlihat. Kontrak yang eksplisit adalah pembeda antara integrasi yang mulus dan malam-malam panjang menebak-nebak.
Merancang skema
Mulailah setiap endpoint dengan menuliskan tiga contoh: request yang valid, response sukses, dan response error. Dari contoh, sarikan skema: daftar field beserta tipe dan batasannya. Prinsip yang membuat skema awet:
- Konsistensi penamaan. Pilih satu gaya —
camelCaseatausnake_case— dan patuhi di seluruh API. Jangan campur. - Tipe yang stabil. Uang sebaiknya string dalam satuan terkecil (
"249000") atau objek{ amount, currency }, bukan float yang rawan pembulatan. Waktu memakai ISO 8601 UTC ("2026-07-11T09:30:00Z"). - ID buram (opaque). Perlakukan ID sebagai string tak bermakna (
"ord_7Fq2") agar Anda bebas mengubah skema internal. Prefiks tipe memudahkan debugging. - Enum eksplisit. Untuk status, definisikan himpunan nilai tertutup dan dokumentasikan; jangan biarkan string bebas.
- Bungkus daftar dalam objek. Kembalikan
{ "data": [...] }, bukan array telanjang — agar Anda bisa menambah metadata (pagination) tanpa merusak kontrak.
Validasi di batas (boundary validation)
Aturan besi: jangan pernah percaya input. Validasi terjadi di batas sistem — begitu request masuk, sebelum menyentuh logika bisnis. Validasi yang baik memeriksa: keberadaan field wajib, tipe, rentang/panjang, format (email, URL), dan aturan bisnis (mis. qty ≥ 1). Alih-alih menulis if berlapis, gunakan pustaka skema. Contoh dengan Zod (TypeScript):
import { z } from "zod";
export const CreateOrderInput = z.object({
productId: z.string().min(1),
qty: z.number().int().positive().max(99),
note: z.string().max(280).optional(),
});
export type CreateOrderInput = z.infer<typeof CreateOrderInput>;
// di handler:
const parsed = CreateOrderInput.safeParse(req.body);
if (!parsed.success) {
return res.status(422).json(toErrorBody(parsed.error));
}
const input = parsed.data; // aman & bertipe
Keuntungan pendekatan skema: satu sumber kebenaran untuk validasi dan tipe, pesan error terstruktur otomatis, dan — seperti akan kita lihat di Bab 7 dan 12 — skema yang sama bisa diturunkan menjadi tipe klien dan dokumentasi OpenAPI.
Format error yang konsisten
Ini mungkin keputusan paling berdampak dalam seluruh desain API Anda: semua error, di semua endpoint, memiliki bentuk yang sama. Ketika error selalu terlihat serupa, konsumen menulis satu penangan error dan selesai. Standar yang layak ditiru adalah RFC 9457 — Problem Details for HTTP APIs (penerus RFC 7807):
Response error — Problem Details (RFC 9457)HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.app.dev/errors/validation",
"title": "Validasi gagal",
"status": 422,
"detail": "Beberapa field tidak valid.",
"instance":"/v1/orders",
"code": "validation_error",
"errors": [
{ "field": "qty", "message": "harus ≥ 1" },
{ "field": "productId", "message": "wajib diisi" }
],
"request_id": "req_5t9x"
}
Field kuncinya: code yang stabil dan bisa dibaca mesin (jangan mengandalkan title yang bisa berubah kata-katanya), errors per-field untuk menyorot input yang salah, dan request_id untuk menghubungkan laporan pengguna dengan log server. Kita bahas anatominya lebih dalam di Bab 6.
Tuliskan bentuk error Anda satu kali di dokumentasi, di paling atas, dan rujuk dari setiap endpoint. Konsumen (dan agen AI) akan berterima kasih karena tidak perlu menebak.
Null, opsional, dan default
Tiga keadaan sering dikacaukan: field tidak ada, field bernilai null, dan field bernilai kosong ("", 0, []). Sepakati maknanya dan konsisten. Rekomendasi: untuk response, sertakan field dengan null alih-alih menghilangkannya, agar bentuk objek selalu sama dan klien tidak perlu memeriksa keberadaan. Untuk request PATCH, bedakan "tidak menyebut field" (jangan ubah) dari "menyebut field dengan null" (kosongkan) — ini keputusan semantik yang harus didokumentasikan.
Data yang buruk mengalahkan kode yang bagus. Struktur data yang benar membuat kode menjadi jelas dengan sendirinya.
Fred Brooks, "The Mythical Man-Month" (parafrasa dari gagasan yang sering dikutip)
Bagian II — Keamanan & Ketahanan
Autentikasi API
API key · OAuth2 · JWT · sesiAutentikasi menjawab "siapa Anda"; otorisasi menjawab "apa yang boleh Anda lakukan". Bab ini fokus pada yang pertama dan menyentuh yang kedua. Memilih mekanisme autentikasi yang tepat bergantung pada siapa konsumen Anda: browser milik pengguna, server milik mitra, atau layanan otomatis. Salah pilih, dan Anda akan memaksakan pola yang tidak pas — atau membuka lubang keamanan.
Empat mekanisme dan kapan dipakai
| Mekanisme | Cocok untuk | Kredensial |
|---|---|---|
| API Key | Server-ke-server, integrasi mitra, skrip. | String rahasia di header. |
| Sesi (cookie) | Web app first-party (frontend & backend satu domain). | Cookie HttpOnly. |
| JWT (Bearer) | API stateless, mobile, antar-microservice. | Token bertanda-tangan di header. |
| OAuth2 | Akses atas nama pengguna oleh aplikasi pihak ketiga. | Access + refresh token via alur delegasi. |
API Key
Paling sederhana: klien menyertakan string rahasia. Cocok saat pemanggilnya adalah mesin, bukan pengguna akhir. Kirim lewat header Authorization, bukan query string (query masuk ke log dan riwayat browser).
curl https://api.app.dev/v1/reports \
-H "Authorization: Bearer sk_live_9f2...c1"
Beri prefiks kunci sesuai lingkungan (sk_live_, sk_test_) agar kebocoran mudah dikenali. Simpan hanya hash kunci di database (seperti password). Dukung rotasi: izinkan beberapa kunci aktif sehingga pengguna bisa membuat kunci baru sebelum mencabut yang lama tanpa downtime.
Sesi berbasis cookie
Untuk aplikasi web di mana frontend dan backend berbagi domain, sesi cookie sering menjadi pilihan paling aman dan paling sederhana. Setelah login, server menetapkan cookie sesi. Karena browser mengirim cookie otomatis, frontend tidak perlu menyimpan token — dan bila cookie diset HttpOnly, JavaScript tidak bisa membacanya, sehingga aman dari pencurian lewat XSS.
Set-Cookie: session=<id>; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1209600
Cookie yang dikirim otomatis membuka risiko Cross-Site Request Forgery. Lindungi operasi tulis dengan SameSite=Lax/Strict dan/atau token anti-CSRF. Untuk API yang murni dipanggil program (bukan browser), token Bearer lebih aman dari CSRF karena tidak dikirim otomatis.
JWT (JSON Web Token)
JWT adalah token mandiri: ia membawa klaim (siapa penggunanya, kapan kedaluwarsa, peran apa) di dalam tubuhnya, ditandatangani secara kriptografis. Server bisa memverifikasi keaslian tanpa menyimpan state — cukup memeriksa tanda tangan. Ini membuat JWT populer untuk API stateless dan komunikasi antar-microservice. Strukturnya tiga bagian dipisah titik: header.payload.signature.
{
"sub": "usr_123", // subject: siapa
"role": "admin",
"iat": 1720688400, // issued-at
"exp": 1720692000 // expiry (pendek! ~15 mnt)
}
JWT sulit dicabut sebelum kedaluwarsa — itulah harga dari sifat stateless. Karena itu: (1) buat access token berumur pendek (menit), dan pasangkan dengan refresh token berumur panjang yang tersimpan aman & bisa dicabut; (2) selalu verifikasi tanda tangan dan tolak algoritma none; (3) jangan menaruh data sensitif di payload — ia hanya di-encode Base64, bukan dienkripsi, jadi bisa dibaca siapa saja.
OAuth2 secara ringkas
OAuth2 dipakai ketika aplikasi pihak ketiga ingin mengakses data pengguna di sistem Anda tanpa pengguna menyerahkan passwordnya. Contoh klasik: "Login dengan Google", atau aplikasi analitik yang membaca data toko Anda. Alur yang direkomendasikan untuk aplikasi web/mobile adalah Authorization Code + PKCE:
- Aplikasi mengarahkan pengguna ke server otorisasi (halaman izin).
- Pengguna menyetujui; server mengembalikan authorization code singkat.
- Aplikasi menukar code (plus code verifier PKCE) dengan access token dan refresh token.
- Aplikasi memanggil API dengan access token; saat kedaluwarsa, ia memakai refresh token untuk memperbarui.
Scope membatasi izin token ("hanya baca order"). Sebagai desainer API, sediakan scope yang bermakna dan sekecil mungkin — prinsip least privilege. Untuk komunikasi server-ke-server tanpa pengguna, alur Client Credentials lebih tepat daripada Authorization Code.
Otorisasi: setelah tahu siapa
Autentikasi hanyalah gerbang pertama. Setiap endpoint yang menyentuh data harus memeriksa apakah pemanggil ini berhak atas resource ini. Kesalahan paling umum dan paling berbahaya adalah Broken Object Level Authorization (BOLA/IDOR): endpoint memeriksa "sudah login?" tetapi lupa memeriksa "apakah order ini miliknya?", sehingga pengguna A bisa membaca /orders/ord_milik_B hanya dengan menebak ID. Selalu tautkan pemeriksaan izin ke pemilik resource, bukan sekadar status login.
Amankan bukan hanya pintu depan, tetapi setiap pintu di dalam rumah. Sebagian besar pelanggaran serius terjadi bukan karena tembok yang jebol, melainkan karena pintu dalam yang tak pernah dikunci.
Prinsip umum keamanan aplikasi, sejalan dengan OWASP API Security Top 10
Keamanan API adalah topik luas; Bab 15 merangkumnya dan merujuk pada buku keamanan tersendiri. Untuk sekarang, ingat urutannya: autentikasi → otorisasi per-objek → baru logika bisnis.
Error Handling & Validasi
konsisten · dapat dibaca mesin · ramahCara sebuah API gagal mengungkap kualitasnya lebih jujur daripada cara ia berhasil. Jalur sukses relatif mudah; jalur error-lah yang menguji apakah desain Anda dipikirkan matang. Bab ini memperdalam format error dari Bab 4 menjadi sistem yang menyeluruh: taksonomi, anatomi, dan disiplin.
Taksonomi error
Kelompokkan error berdasarkan siapa yang harus bertindak:
- Error klien (4xx) — pemanggil harus memperbaiki. Input salah, tidak berizin, resource tak ada, konflik state. Beri pesan yang actionable: apa yang salah dan bagaimana membetulkannya.
- Error server (5xx) — Anda yang harus memperbaiki. Bug, dependensi mati, timeout. Jangan bocorkan detail internal (stack trace, query) ke klien; catat di log, kembalikan
request_iduntuk penelusuran.
Anatomi pesan error yang baik
Kembali ke Problem Details dari Bab 4, mari bedah tiap field dari sudut kegunaannya:
| Field | Untuk siapa | Sifat |
|---|---|---|
status | mesin & manusia | Selaras dengan status HTTP. |
code | mesin | Stabil, snake_case, tak pernah berubah kata. |
title | manusia | Ringkas; boleh berubah kata/bahasa. |
detail | manusia | Penjelasan spesifik untuk kasus ini. |
errors[] | mesin & manusia | Rincian per-field untuk validasi. |
request_id | dukungan | Menautkan ke log server. |
Konsumen membangun logika di atas code ("jika insufficient_funds, tampilkan halaman top-up"). Karena itu code adalah bagian dari kontrak Anda — memperlakukannya seperti API publik. Ubahlah title sesuka hati; jangan pernah diam-diam mengubah code.
Peta status code untuk error umum
Konvensi pemetaan (contoh)400 bad_request # JSON tak bisa di-parse / parameter cacat
401 unauthenticated # token hilang/kadaluarsa
403 forbidden # tidak berizin atas resource
404 not_found # resource tak ada (atau disembunyikan)
409 conflict # bentrok state / duplikat unik
422 validation_error # JSON valid, aturan bisnis dilanggar
429 rate_limited # kena batas; sertakan Retry-After
500 internal_error # bug kita; log + request_id
503 service_unavailable# down/maintenance; Retry-After
Menangani error secara terpusat
Jangan tebar try/catch dengan format berbeda di tiap handler. Sentralkan: lempar error bertipe, tangkap di satu middleware, dan format seragam di sana. Pola yang bersih:
export class ApiError extends Error {
constructor(
public status: number,
public code: string,
public detail?: string,
public errors?: unknown[],
) { super(code); }
}
// dilempar di mana saja:
throw new ApiError(404, "not_found", "Order tidak ditemukan");
// satu tempat memformatnya:
export function errorHandler(err, req, res, next) {
const e = err instanceof ApiError
? err
: new ApiError(500, "internal_error");
if (e.status >= 500) logger.error({ err, reqId: req.id });
res.status(e.status).json({
type: `https://api.app.dev/errors/${e.code}`,
title: titleFor(e.code),
status: e.status,
code: e.code,
detail: e.detail,
errors: e.errors,
request_id: req.id,
});
}
Validasi: pesan yang menuntun, bukan menghakimi
Pesan validasi yang baik memberi tahu field mana, apa yang salah, dan seperti apa yang benar. Bandingkan:
- Buruk:
"Invalid input"— tak tahu apa yang salah. - Sedang:
"qty invalid"— tahu field, tak tahu aturannya. - Baik:
{ "field": "qty", "message": "harus bilangan bulat 1–99" }.
Ada ketegangan antara ramah dan aman. Pada login, jangan bedakan "email tidak terdaftar" dari "password salah" — itu membocorkan keberadaan akun. Kembalikan pesan generik "kredensial salah". Ramah untuk validasi form, tetapi hati-hati untuk hal yang menyangkut privasi/keamanan.
Rancang antarmuka Anda agar mudah dipakai benar dan sulit dipakai salah.
Scott Meyers, prinsip desain antarmuka yang sering dikutip
Bagian III — Paradigma Lain
tRPC — Type-Safe End-to-End
TypeScript · tanpa kontrak terpisahREST menempatkan kontrak di tengah dua dunia yang bisa ditulis dalam bahasa apa pun. Tetapi bila frontend dan backend Anda sama-sama TypeScript, ada peluang menghapus lapisan kontrak manual: biarkan tipe mengalir langsung dari server ke klien. Itulah janji tRPC — Anda memanggil fungsi backend seolah-olah fungsi lokal, lengkap dengan autocomplete dan pengecekan tipe, tanpa menulis skema REST atau kode klien.
Ide inti
Di tRPC, Anda mendefinisikan procedure di server (query untuk baca, mutation untuk tulis). Tipe dari router server di-infer dan diimpor sebagai tipe di klien. Tidak ada file kontrak, tidak ada codegen: TypeScript sendiri yang menjadi kontraknya. Jika Anda mengubah tipe kembalian sebuah procedure, kode klien yang memakainya langsung merah di editor — sebelum runtime.
server/router.tsimport { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
export const appRouter = t.router({
order: t.router({
byId: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.orders.find(input.id); // tipe kembalian terinfer
}),
create: t.procedure
.input(z.object({ productId: z.string(), qty: z.number().int().positive() }))
.mutation(async ({ input }) => {
return db.orders.create(input);
}),
}),
});
export type AppRouter = typeof appRouter; // hanya TIPE yang diekspor ke klien
client/order.ts
import type { AppRouter } from "../server/router";
import { createTRPCClient } from "@trpc/client";
const api = createTRPCClient<AppRouter>({ /* ... */ });
// autocomplete penuh + bertipe, tanpa menulis fetch/URL:
const order = await api.order.byId.query({ id: "ord_7Fq2" });
await api.order.create.mutate({ productId: "sku_42", qty: 2 });
Kapan tRPC pas
| Pilih tRPC bila… | Hindari tRPC bila… |
|---|---|
| Frontend & backend satu monorepo TypeScript. | Konsumen memakai banyak bahasa (mobile native, mitra). |
| Anda ingin iterasi cepat tanpa memelihara kontrak. | Anda butuh API publik yang stabil & terdokumentasi. |
| Tim kecil, kendali penuh atas kedua sisi. | Butuh caching HTTP standar / kompatibilitas tooling REST. |
Di baliknya, tRPC tetap HTTP — biasanya POST ke satu endpoint dengan nama procedure. Ia bukan "pengganti REST" secara universal, melainkan optimasi developer experience untuk kasus TypeScript-penuh. Banyak tim memakai tRPC untuk konsumsi internal sendiri, dan tetap menyediakan REST/OpenAPI untuk pihak luar.
Batasan yang jujur
tRPC menukar universalitas dengan kemulusan. Karena "kontrak"-nya adalah tipe TypeScript, ia hanya berguna bagi konsumen TypeScript yang berbagi kode. Untuk agen AI atau mitra eksternal yang membaca dokumentasi, Anda tetap butuh deskripsi eksplisit — di sinilah plugin seperti trpc-openapi membantu menjembatani ke OpenAPI (Bab 12). Aturan pilihnya sederhana: tRPC untuk dalam rumah, REST/OpenAPI untuk tamu.
GraphQL
schema · query/mutation/subscription · N+1REST mengembalikan resource dalam bentuk yang ditentukan server; klien mengambil apa adanya, kadang terlalu banyak (over-fetching) atau terlalu sedikit sehingga harus memanggil berkali-kali (under-fetching). GraphQL membalik kendali: klien menyatakan persis field apa yang diinginkan, dalam satu permintaan, dan server memenuhinya. Ini bersinar ketika data saling terhubung dan kebutuhan klien beragam.
Schema: satu bahasa untuk seluruh graf
Inti GraphQL adalah schema bertipe kuat yang mendeskripsikan seluruh data dan operasi. Klien dan server sama-sama merujuk pada schema ini; ia juga menjadi dokumentasi yang bisa dieksplorasi (introspection).
schema.graphqltype Order {
id: ID!
status: OrderStatus!
total: String!
items: [OrderItem!]! # relasi
customer: Customer!
}
enum OrderStatus { PENDING CONFIRMED CANCELLED }
type Query {
order(id: ID!): Order
orders(status: OrderStatus, first: Int, after: String): OrderConnection!
}
type Mutation {
createOrder(input: CreateOrderInput!): Order!
}
type Subscription {
orderUpdated(id: ID!): Order!
}
Query, mutation, subscription
GraphQL memiliki tiga jenis operasi. Query untuk membaca — klien memilih tepat field yang diperlukan, menembus relasi dalam satu perjalanan:
Query — ambil tepat yang dibutuhkanquery {
order(id: "ord_7Fq2") {
status
total
customer { name }
items { name qty }
}
}
Mutation untuk menulis (buat/ubah/hapus), dan mengembalikan bentuk hasil yang juga dipilih klien. Subscription untuk realtime: klien berlangganan peristiwa dan server mendorong update lewat koneksi persisten (umumnya WebSocket) — jembatan alami ke Bab 11.
Masalah N+1 dan solusinya
Fleksibilitas GraphQL punya jebakan klasik. Ketika klien meminta daftar 50 order beserta customer masing-masing, resolver naif akan menjalankan 1 kueri untuk daftar order lalu 50 kueri terpisah untuk tiap customer — total 51 kueri. Inilah masalah N+1, penyebab utama GraphQL lambat di produksi.
Solusi standarnya adalah pola DataLoader: kumpulkan semua permintaan customer dalam satu "tick", gabungkan menjadi satu kueri batch (WHERE id IN (...)), lalu bagikan hasilnya. 51 kueri menyusut jadi 2. DataLoader juga men-cache dalam satu request agar id yang sama tak diambil dua kali.
Harga yang dibayar
GraphQL bukan makan siang gratis. Beberapa hal yang di REST otomatis, di GraphQL menjadi tanggung jawab Anda:
- Caching HTTP lebih sulit — karena semua lewat satu endpoint POST, cache berbasis URL/ETag tak langsung bekerja; Anda mengandalkan cache di level klien atau persisted queries.
- Kompleksitas kueri harus dibatasi — klien jahat bisa meminta graf yang sangat dalam/mahal. Terapkan depth limiting, cost analysis, dan timeout.
- Otorisasi per-field lebih rumit — tiap field bisa punya aturan izin sendiri.
- Status error — GraphQL cenderung mengembalikan
200 OKdengan arrayerrorsdi body, sehingga penanganan error berbeda dari REST.
REST vs GraphQL vs tRPC — memilih dengan sadar
| Kriteria | REST | GraphQL | tRPC |
|---|---|---|---|
| Konsumen lintas bahasa | Sangat baik | Baik | Buruk |
| Fleksibilitas pengambilan data | Terbatas | Sangat baik | Sedang |
| Caching HTTP bawaan | Sangat baik | Sulit | Terbatas |
| Kesederhanaan awal | Baik | Kompleks | Sangat baik* |
| Type-safety end-to-end | Via OpenAPI | Via codegen | Otomatis |
*bila kedua sisi TypeScript. Tidak ada pemenang mutlak. Banyak sistem sehat memakai kombinasi: REST untuk API publik, GraphQL untuk aggregator yang melayani banyak layar berbeda, tRPC untuk internal TypeScript.
Setiap masalah desain punya beberapa solusi yang benar; tugas kita memilih yang paling tidak salah untuk konteks kita.
Pepatah rekayasa perangkat lunak
Webhooks
kirim · terima · signature · retrySejauh ini klien selalu yang bertanya dan server yang menjawab. Tetapi bagaimana bila server perlu memberi tahu klien tentang sesuatu yang baru terjadi — pembayaran berhasil, pesanan dikirim — tanpa klien harus terus-menerus bertanya? Jawabannya adalah webhook: API yang dibalik. Server Anda memanggil URL milik konsumen ketika sebuah peristiwa terjadi. Ini mengubah pola "tarik" (polling) menjadi "dorong" (push) yang efisien.
Anatomi sebuah webhook
Konsumen mendaftarkan sebuah endpoint URL dan memilih peristiwa yang diminati. Saat peristiwa terjadi, Anda mengirim POST ke URL itu berisi event payload. Desain payload yang baik menyertakan: jenis event, ID event unik (untuk deduplikasi), timestamp, dan data terkait.
Payload webhook yang dikirim ke konsumenPOST https://shop.mitra.com/hooks/app HTTP/1.1
Content-Type: application/json
Webhook-Id: evt_8k2p
Webhook-Timestamp: 1720688400
Webhook-Signature: v1,3Nq8...c2==
{
"id": "evt_8k2p",
"type": "order.paid",
"created": "2026-07-11T09:30:00Z",
"data": { "orderId": "ord_7Fq2", "amount": "249000" }
}
Signature: membuktikan pengirim
Endpoint webhook konsumen bersifat publik — siapa pun bisa mengirim POST palsu ke sana. Karena itu setiap webhook harus ditandatangani. Pola umum: Anda dan konsumen berbagi sebuah signing secret; Anda menghitung HMAC-SHA256 atas (timestamp + body) dan menaruhnya di header. Konsumen menghitung ulang dan membandingkan.
Sisi PENGIRIM — menandatangani (Node.js)import crypto from "node:crypto";
function sign(secret, timestamp, rawBody) {
const signed = `${timestamp}.${rawBody}`;
return "v1," + crypto
.createHmac("sha256", secret)
.update(signed)
.digest("base64");
}
Sisi PENERIMA — memverifikasi (Node.js)
function verify(req, secret) {
const ts = req.header("Webhook-Timestamp");
const sig = req.header("Webhook-Signature");
// 1) tolak yang terlalu lama (anti-replay)
if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return false;
// 2) hitung ulang atas RAW body (bukan hasil parse!)
const expected = sign(secret, ts, req.rawBody);
// 3) bandingkan konstan-waktu
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
(1) Verifikasi atas raw body byte-per-byte — bila Anda mem-parse JSON dulu lalu meng-stringify ulang, urutan/format berubah dan tanda tangan gagal. (2) Pakai perbandingan konstan-waktu (timingSafeEqual) untuk mencegah timing attack. (3) Sertakan dan periksa timestamp agar penyerang tak bisa memutar ulang (replay) pesan lama yang valid.
Retry dan keandalan pengiriman
URL konsumen bisa sedang mati saat Anda mengirim. Webhook yang andal karena itu mencoba ulang dengan exponential backoff: gagal → coba lagi setelah 10 detik, 1 menit, 10 menit, 1 jam, dst., hingga batas tertentu, lalu tandai gagal dan beri tahu konsumen. Konsekuensinya bagi desain: pengiriman bersifat at-least-once — satu event bisa sampai lebih dari sekali.
Sebagai pengirim: anggap sukses hanya bila menerima 2xx; retry pada selain itu; sediakan halaman untuk konsumen melihat riwayat & mengirim ulang manual. Sebagai penerima: balas 2xx secepatnya lalu proses secara asinkron (jangan buat pengirim menunggu pekerjaan berat), dan deduplikasi berdasarkan Webhook-Id karena event bisa berulang. Idempotensi (Bab 10) berlaku penuh di sini.
Webhook vs polling vs realtime
Webhook cocok untuk komunikasi server-ke-server saat peristiwa jarang tetapi penting (pembayaran, status pengiriman). Untuk update ke browser pengguna secara langsung, WebSocket atau SSE lebih tepat (Bab 11). Polling — klien bertanya berkala — adalah pilihan terakhir: sederhana tetapi boros dan lambat. Sebagai penyedia yang baik, tawarkan webhook agar konsumen tak perlu polling.
Dalam sistem terdistribusi, pengiriman "tepat sekali" adalah ilusi yang mahal. Yang realistis adalah "setidaknya sekali" ditambah operasi yang idempoten.
Prinsip umum sistem terdistribusi
Bagian IV — Skala & Realtime
Rate Limiting, Caching & Idempotency-Key
429 · ETag · aman diulangAPI yang sukses akan dibebani. Tiga mekanisme ini — pembatasan laju, caching, dan kunci idempotensi — adalah alat pertahanan dan efisiensi yang mengubah API rapuh menjadi API tangguh. Ketiganya bekerja lewat header HTTP standar, jadi menerapkannya tidak butuh protokol khusus, hanya disiplin.
Rate limiting
Pembatasan laju melindungi API dari penyalahgunaan dan menjamin keadilan antar-konsumen. Ketika sebuah klien melampaui kuota, kembalikan 429 Too Many Requests dan beri tahu kapan boleh mencoba lagi. Sertakan header kuota di setiap response agar klien bisa mengatur diri:
HTTP/1.1 200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 12
RateLimit-Reset: 30 # detik sampai kuota pulih
# ketika habis:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Algoritma umum: token bucket (mengisi ulang token pada laju tetap; tiap request memakai satu — mengizinkan burst kecil), fixed/sliding window (menghitung request per jendela waktu). Untuk sistem terdistribusi, hitungan biasanya disimpan di store bersama seperti Redis agar konsisten lintas server.
Batasi berdasarkan identitas yang bermakna — API key atau user, bukan hanya IP (banyak pengguna bisa berbagi satu IP di belakang NAT). Terapkan batas berbeda per tier langganan, dan beri jalur "kuota lebih tinggi" untuk mitra tepercaya.
Caching dengan ETag
Cara tercepat menjawab sebuah request adalah tidak memprosesnya sama sekali. Caching HTTP memungkinkan itu. Untuk data yang bisa berubah, pola validasi dengan ETag sangat ampuh: server mengirim sidik jari (hash) dari representasi; klien menyimpannya dan, pada permintaan berikutnya, bertanya "apakah masih sama?" via If-None-Match. Jika ya, server membalas 304 Not Modified tanpa body — menghemat bandwidth dan render.
# response pertama:
GET /v1/orders/ord_7Fq2 -> 200 OK
ETag: "a1b2c3"
# permintaan berikutnya, klien menyertakan ETag:
GET /v1/orders/ord_7Fq2
If-None-Match: "a1b2c3"
-> 304 Not Modified # hemat: tanpa body
Untuk data statis/publik, gunakan Cache-Control: max-age=... agar klien/CDN menyimpan salinan tanpa bertanya sama sekali. Bedakan public (boleh di-cache CDN) dari private (hanya browser pengguna, mis. data personal).
ETag untuk optimistic concurrency
ETag bukan hanya soal cache. Ia juga menyelesaikan masalah "dua orang mengedit bersamaan". Klien mengirim PUT/PATCH dengan If-Match: "<etag>". Jika sementara itu resource sudah diubah orang lain (ETag tak cocok lagi), server membalas 412 Precondition Failed — mencegah menimpa perubahan orang lain secara diam-diam.
Idempotency-Key
Kita berjanji di Bab 2 untuk kembali ke ini. Masalahnya: POST tidak idempoten, tetapi jaringan memaksa klien retry. Solusinya elegan: klien menghasilkan sebuah kunci unik (UUID) per operasi dan mengirimnya di header Idempotency-Key. Server menyimpan hasil pertama untuk kunci itu; bila kunci sama datang lagi, server mengembalikan hasil tersimpan alih-alih memproses ulang.
async function createOrder(req, res) {
const key = req.header("Idempotency-Key");
if (key) {
const saved = await store.get(key);
if (saved) return res.status(saved.status).json(saved.body); // replay
}
const order = await db.orders.create(req.body);
const result = { status: 201, body: order };
if (key) await store.set(key, result, { ttl: "24h" });
res.status(201).json(order);
}
Simpan hasil dengan kunci untuk jangka waktu wajar (mis. 24 jam) dan tautkan pada pemilik agar kunci tak bisa "dipinjam" lintas pengguna. Tangani balapan: bila dua request dengan kunci sama tiba nyaris bersamaan, kunci baris/gunakan penyimpanan atomik agar hanya satu yang membuat order. Payment API mewajibkan pola ini justru karena uang tidak boleh terpotong dua kali.
Ada dua hal sulit dalam ilmu komputer: invalidasi cache, penamaan, dan kesalahan hitung.
Phil Karlton (versi lelucon yang tersohor)
Realtime — WebSocket & SSE
push · dua arah vs satu arahModel request/response cocok untuk "tanya-jawab", tetapi banyak fitur modern butuh "server memberi tahu segera": notifikasi, chat, harga live, kolaborasi. Ada dua teknologi utama untuk itu di web — Server-Sent Events (SSE) dan WebSocket. Memilih di antaranya adalah soal arah dan kompleksitas.
Server-Sent Events (SSE)
SSE adalah aliran satu arah: server terus mendorong pesan ke klien lewat satu koneksi HTTP yang tetap terbuka. Ia berjalan di atas HTTP biasa, ringan, dan punya fitur bawaan yang manis: reconnect otomatis dan event id untuk melanjutkan dari titik terputus. Cocok untuk notifikasi, feed, progress, dan "harga saham berjalan".
Server SSE (Node.js)res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
setInterval(() => {
res.write(`id: ${Date.now()}\n`);
res.write(`event: price\n`);
res.write(`data: ${JSON.stringify({ sym: "IDR", v: 15980 })}\n\n`);
}, 1000);
Klien SSE (browser)
const es = new EventSource("/v1/prices/stream");
es.addEventListener("price", (e) => {
const p = JSON.parse(e.data);
render(p);
});
// reconnect otomatis bila terputus — bawaan EventSource
WebSocket
WebSocket membuka kanal dua arah penuh (full-duplex): klien dan server sama-sama bisa mengirim kapan saja lewat satu koneksi persisten. Ia dimulai dari HTTP lalu "naik kelas" (upgrade) ke protokol ws:///wss://. Cocok saat klien juga sering mengirim — chat, game, kolaborasi dokumen, editor bersama.
const ws = new WebSocket("wss://api.app.dev/v1/chat");
ws.onopen = () => ws.send(JSON.stringify({ type: "join", room: "r1" }));
ws.onmessage = (e) => append(JSON.parse(e.data));
ws.onclose = () => scheduleReconnect(); // reconnect harus Anda tangani sendiri
Memilih: SSE vs WebSocket vs polling
| Kriteria | SSE | WebSocket | Long-polling |
|---|---|---|---|
| Arah | Server → klien | Dua arah | Server → klien (tiruan) |
| Protokol | HTTP biasa | ws/wss (upgrade) | HTTP biasa |
| Reconnect bawaan | Ya | Tidak (buat sendiri) | Tidak |
| Kompleksitas | Rendah | Sedang–tinggi | Rendah |
| Pas untuk | Notifikasi, feed | Chat, game, kolaborasi | Fallback lawas |
Bila hanya server yang perlu bicara, pilih SSE — lebih sederhana dan tahan banting. Bila klien juga sering mengirim dengan latensi rendah, pilih WebSocket. Jangan pakai WebSocket "karena keren"; koneksi persisten dua arah menambah beban skala (state per-koneksi, sticky session, penskalaan horizontal yang lebih rumit).
Tantangan skala realtime
Koneksi persisten menyimpan state di server tertentu. Saat Anda punya banyak server, pesan yang lahir di server A harus sampai ke klien yang terhubung di server B. Solusinya lazim: sebuah pub/sub broker (Redis, NATS) sebagai tulang punggung, di mana tiap server berlangganan kanal dan meneruskan ke koneksi lokalnya. Selain itu, siapkan heartbeat/ping untuk mendeteksi koneksi mati, dan batasi jumlah koneksi per pengguna.
Realtime bukan tentang kecepatan semata, melainkan tentang mengubah model mental dari 'menarik ketika butuh' menjadi 'diberi tahu ketika berubah'.
Prinsip desain sistem berbasis peristiwa
OpenAPI & Dokumentasi Otomatis
satu spesifikasi · banyak keluaranDokumentasi yang ditulis tangan selalu ketinggalan dari kode. Solusi paling awet adalah menjadikan spesifikasi sebagai sumber kebenaran yang darinya dokumentasi, tipe klien, dan bahkan tes diturunkan otomatis. Untuk REST, standar itu bernama OpenAPI (dulu Swagger). Satu file, banyak keluaran.
Apa itu OpenAPI
OpenAPI adalah format (YAML/JSON) untuk mendeskripsikan API REST secara mesin-terbaca: path, metode, parameter, skema request/response, kode status, dan skema keamanan. Dari satu dokumen ini Anda bisa menghasilkan: halaman dokumentasi interaktif (Swagger UI, Redoc), SDK klien dalam banyak bahasa, stub server, koleksi Postman, dan validasi request otomatis di gateway.
openapi.yaml — potongan untuk POST /v1/ordersopenapi: 3.1.0
info: { title: App API, version: "1.0.0" }
paths:
/v1/orders:
post:
summary: Buat order baru
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CreateOrderInput" }
responses:
"201":
description: Order dibuat
content:
application/json:
schema: { $ref: "#/components/schemas/Order" }
"422":
description: Validasi gagal
content:
application/problem+json:
schema: { $ref: "#/components/schemas/Problem" }
components:
schemas:
CreateOrderInput:
type: object
required: [productId, qty]
properties:
productId: { type: string }
qty: { type: integer, minimum: 1, maximum: 99 }
Code-first vs spec-first
Dua filosofi menyusun OpenAPI:
- Spec-first: tulis
openapi.yamllebih dulu sebagai kesepakatan, lalu hasilkan stub server & klien. Bagus untuk kolaborasi lintas tim dan API publik — kontrak disepakati sebelum kode. - Code-first: anotasi/skema di kode (mis. Zod +
zod-to-openapi, atau dekorator di NestJS) menghasilkan spec otomatis. Bagus untuk menjaga spec selalu sinkron dengan implementasi.
Ingat skema Zod dari Bab 4? Dengan zod-to-openapi, skema validasi yang sama menghasilkan entri OpenAPI. Anda memvalidasi, mengetik, dan mendokumentasikan dari satu definisi — mustahil untuk "lupa memperbarui dokumen" karena dokumen dan validasi adalah objek yang sama.
Dokumentasi yang benar-benar berguna
Spec formal saja belum cukup ramah manusia. Dokumentasi API terbaik menambahkan: panduan mulai cepat (authentication dulu), contoh curl yang bisa disalin untuk setiap endpoint, penjelasan format error di satu tempat (rujuk Bab 6), dan catatan versi/deprecation. Untuk agen AI, spec OpenAPI yang lengkap justru menjadi bahan bakar utama — dibahas di Bab 17.
Server Actions (Next.js) & BFF
tanpa endpoint eksplisit · lapisan penyesuaiFramework modern mengaburkan garis antara "memanggil API" dan "memanggil fungsi". Next.js Server Actions memungkinkan frontend memanggil fungsi server secara langsung tanpa Anda menuliskan endpoint REST. Sementara pola BFF (Backend for Frontend) menempatkan lapisan tipis yang disesuaikan untuk tiap klien. Keduanya adalah cara memikirkan batas frontend–backend yang layak dipahami.
Server Actions
Server Action adalah fungsi bertanda "use server" yang berjalan di server tetapi bisa dipanggil dari komponen klien seolah lokal. Framework meng-generate endpoint dan serialisasi di baliknya. Ini menghapus banyak boilerplate untuk mutasi form pada aplikasi Next.js.
"use server";
import { z } from "zod";
const Input = z.object({ productId: z.string(), qty: z.coerce.number().int().positive() });
export async function createOrder(formData: FormData) {
const parsed = Input.safeParse(Object.fromEntries(formData));
if (!parsed.success) return { ok: false, errors: parsed.error.flatten() };
const order = await db.orders.create(parsed.data);
return { ok: true, order };
}
"Terlihat seperti fungsi lokal" bukan berarti aman secara otomatis. Server Action tetap endpoint yang bisa dipanggil siapa saja dengan input apa saja. Anda wajib: memvalidasi input (seperti di atas), memeriksa autentikasi & otorisasi di dalam action, dan tidak mengandalkan "hanya UI kami yang memanggilnya". Semua pelajaran Bab 4–6 berlaku penuh.
Pola BFF (Backend for Frontend)
Ketika satu API backend melayani banyak jenis klien (web, mobile, mitra), memaksakan satu bentuk response untuk semua sering menyakitkan — mobile ingin payload ramping, web ingin data kaya. BFF menyisipkan lapisan tipis per-klien: setiap frontend punya "backend"-nya sendiri yang menggabungkan, memangkas, dan menyesuaikan panggilan ke layanan inti.
Manfaat BFF: setiap klien mendapat bentuk data ideal, autentikasi bisa disesuaikan (cookie untuk web, token untuk mobile), dan perubahan pada satu klien tak mengganggu yang lain. Biayanya: lebih banyak kode untuk dipelihara. Server Actions Next.js, menariknya, adalah bentuk BFF yang menyatu dengan framework — logika penyesuai untuk web hidup berdampingan dengan komponennya.
Perangkat lunak yang baik menempatkan setiap kompleksitas di tempat ia paling murah untuk ditangani.
Prinsip desain arsitektur perangkat lunak
Menguji API
contract test · curl · PostmanAPI yang tak diuji adalah janji yang belum tentu ditepati. Pengujian API berbeda dari pengujian unit biasa: fokusnya pada kontrak — apakah bentuk request/response, status code, dan perilaku sesuai kesepakatan. Bab ini menata lapisan-lapisan pengujian dari yang cepat sampai yang menyeluruh.
Piramida pengujian API
- Unit — logika bisnis murni (perhitungan total, aturan validasi) tanpa jaringan. Cepat, banyak.
- Integrasi/HTTP — jalankan server, tembak endpoint sungguhan dengan klien HTTP, periksa status & body. Menangkap masalah routing, serialisasi, middleware.
- Contract test — pastikan API mematuhi kontrak (mis. spec OpenAPI) dan konsumen mengharapkan yang benar. Mencegah "backend berubah diam-diam".
- End-to-end — alur nyata lintas beberapa endpoint (buat → bayar → kirim). Sedikit, tetapi berharga.
Menguji cepat dengan curl
Sebelum menulis tes formal, biasakan menembak endpoint dengan curl. Ini juga dokumentasi hidup yang bisa Anda tempel ke README.
# sukses:
curl -sS -X POST https://api.app.dev/v1/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "productId": "sku_42", "qty": 2 }' -i
# validasi gagal (harapkan 422):
curl -sS -X POST https://api.app.dev/v1/orders \
-H "Authorization: Bearer $TOKEN" \
-d '{ "qty": -1 }' -w "\n%{http_code}\n"
Tes integrasi HTTP
order.test.ts — Vitest + supertestimport { describe, it, expect } from "vitest";
import request from "supertest";
import { app } from "../app";
describe("POST /v1/orders", () => {
it("membuat order dan mengembalikan 201 + Location", async () => {
const res = await request(app)
.post("/v1/orders")
.set("Authorization", "Bearer test")
.send({ productId: "sku_42", qty: 2 });
expect(res.status).toBe(201);
expect(res.headers.location).toMatch(/\/v1\/orders\//);
expect(res.body).toMatchObject({ status: "confirmed" });
});
it("menolak qty invalid dengan 422 + code stabil", async () => {
const res = await request(app).post("/v1/orders")
.set("Authorization", "Bearer test").send({ qty: -1 });
expect(res.status).toBe(422);
expect(res.body.code).toBe("validation_error");
});
});
Contract testing terhadap OpenAPI
Karena Anda punya spec OpenAPI (Bab 12), Anda bisa memvalidasi bahwa response nyata cocok dengan skema yang dijanjikan. Pustaka seperti Dredd atau validator berbasis skema akan menembak setiap endpoint dan gagal bila bentuk response menyimpang dari spec. Ini menutup celah paling licik: dokumentasi mengatakan A, server mengembalikan B.
Ekspor OpenAPI Anda ke koleksi Postman (atau Bruno yang berbasis file & ramah git). Simpan koleksi di repo agar tim punya "sandbox" bersama untuk mencoba endpoint. Jalankan koleksi di CI dengan newman untuk smoke test tiap deploy.
Pengujian dapat menunjukkan adanya bug, tetapi tak pernah menunjukkan ketiadaannya. Meski begitu, ia satu-satunya bukti empiris yang kita punya.
Edsger W. Dijkstra, "Notes on Structured Programming" (1970)
Keamanan API Ringkas
OWASP API Top 10 · pertahanan lapisKeamanan bukan fitur yang ditambahkan di akhir; ia adalah cara berpikir sepanjang desain. Bab ini merangkum risiko API yang paling sering dieksploitasi dan pertahanannya, sebagai peta ringkas. Untuk pembahasan mendalam — kriptografi, ancaman lanjutan, hardening infrastruktur — rujuk buku keamanan tersendiri dalam seri ini.
Risiko teratas dan penangkalnya
| Risiko | Inti masalah | Pertahanan |
|---|---|---|
| BOLA / IDOR | Akses objek milik orang lain dengan menebak ID. | Cek kepemilikan per-objek, bukan sekadar login (Bab 5). |
| Auth rusak | Token lemah, tanpa expiry, algoritma none. | Token pendek + refresh, verifikasi tanda tangan ketat. |
| Data berlebih | Response membocorkan field sensitif. | Whitelist field keluaran; jangan kirim seluruh baris DB. |
| Tanpa rate limit | Brute force, scraping, DoS. | Rate limit per identitas (Bab 10). |
| Mass assignment | Klien mengubah field terlarang (mis. role). | Validasi & petakan hanya field yang diizinkan. |
| Injeksi | SQL/NoSQL/command injection dari input. | Query berparameter, allowlist, escaping. |
| Misconfiguration | CORS longgar, error bocor stack trace. | Default aman, sembunyikan detail internal. |
Tujuh disiplin dasar
- Selalu HTTPS. Tanpa TLS, token dan data telanjang di jaringan. Tolak HTTP.
- Validasi setiap input di batas (Bab 4) — termasuk header, query, dan path, bukan hanya body.
- Least privilege. Token & kunci hanya diberi scope seminimal mungkin.
- Jangan bocorkan. Error 5xx generik ke klien; detail hanya ke log internal (Bab 6).
- Batasi & pantau. Rate limit, log terstruktur dengan
request_id, alarm pada anomali. - Kelola rahasia. Simpan secret di secret manager, bukan di kode/git. Rotasi berkala.
- CORS sadar. Izinkan hanya origin yang Anda percayai; jangan
*untuk endpoint berkredensial.
Pola berbahaya: db.users.update(id, req.body) — pengguna cukup menambahkan { "role": "admin" } ke body untuk mempromosikan diri. Selalu petakan eksplisit: ambil hanya field yang boleh diubah ({ name, avatar }) dari input. Skema Zod dari Bab 4 mencegah ini secara alami karena hanya field terdefinisi yang lolos.
Ini hanya ringkasan. Keamanan API adalah bidang tersendiri yang berkembang cepat. Perlakukan tabel di atas sebagai checklist minimum, bukan garis akhir — dan lakukan tinjauan keamanan berkala.
Keamanan bukanlah produk, melainkan sebuah proses.
Bruce Schneier, "Secrets and Lies" (2000)
Bagian V — Praktik
Contoh Nyata: API untuk SaaS
rancang · lalu coba Endpoint DesignerSemua prinsip dari bab-bab sebelumnya kini kita rangkai menjadi satu desain nyata: API untuk sebuah SaaS manajemen tugas bernama Taskflow. Kita akan menelusuri keputusan desainnya, lalu Anda bisa bereksperimen sendiri dengan Endpoint Designer di akhir bab.
Domain dan resource
Taskflow punya konsep inti: workspace (ruang kerja tim), project (di dalam workspace), task (di dalam project), dan member. Kita modelkan sebagai resource REST dengan versi di path:
Peta endpoint Taskflow v1POST /v1/workspaces # buat workspace
GET /v1/workspaces/{wid}/projects # daftar project (cursor)
POST /v1/workspaces/{wid}/projects # buat project
GET /v1/projects/{pid}/tasks # filter=?status&assignee
POST /v1/projects/{pid}/tasks # buat task (Idempotency-Key)
PATCH /v1/tasks/{tid} # ubah status/judul (If-Match)
DELETE /v1/tasks/{tid} # hapus -> 204
Perhatikan keputusan: task di-nest di bawah project untuk pembuatan/daftar (relasi milik jelas), tetapi PATCH/DELETE memakai path datar /v1/tasks/{tid} karena ID task sudah unik global — menghindari nesting berlebih. Filter ?status=open&assignee=usr_9 memakai konvensi Bab 3.
Kontrak sebuah task
Response GET /v1/tasks/{tid}{
"id": "task_3af",
"projectId": "proj_12",
"title": "Rancang skema API",
"status": "in_progress",
"assignee": { "id": "usr_9", "name": "Rani" },
"dueAt": "2026-07-20T00:00:00Z",
"createdAt": "2026-07-11T09:30:00Z",
"_links": { "self": { "href": "/v1/tasks/task_3af" } }
}
Keputusan kontrak yang kita ambil, merujuk Bab 4: penamaan camelCase konsisten, waktu ISO 8601 UTC, status sebagai enum tertutup (open, in_progress, done, archived), assignee diperluas menjadi objek ringkas (bukan hanya ID) untuk menghemat perjalanan bolak-balik, dan tautan _links ala HATEOAS ringan.
Lintas-fitur yang menyatu
- Auth (Bab 5): API key untuk integrasi CI, sesi cookie untuk web app, semua operasi memeriksa keanggotaan workspace (otorisasi per-objek).
- Error (Bab 6): semua endpoint mengembalikan Problem Details dengan
codestabil. - Idempotensi (Bab 10): pembuatan task menerima
Idempotency-Key; pembaruan memakaiIf-MatchETag untuk mencegah bentrok editor. - Realtime (Bab 11): SSE di
/v1/projects/{pid}/eventsmendorong perubahan task ke papan kanban. - Webhook (Bab 9): event
task.completeddikirim ke URL integrasi pelanggan, ditandatangani HMAC. - Docs (Bab 12): OpenAPI dihasilkan dari skema Zod; Swagger UI publik.
Endpoint Designer
Kini giliran Anda. Alat di bawah mengubah deskripsi endpoint dalam JSON menjadi tiga keluaran siap-pakai: perintah curl, bentuk request/response, dan cuplikan OpenAPI. Tempel JSON dengan kunci method, path, params, body, dan response — atau muat salah satu contoh. Semuanya berjalan di browser Anda; tak ada data yang dikirim ke mana pun.
method: string (GET/POST/…). path: string, boleh memakai {param}. params: objek pasangan nama→contoh untuk query. body: objek contoh request. response: objek contoh response sukses. Semua kunci opsional kecuali method dan path. Bila JSON tak valid, alat akan memberi tahu apa yang salah.
Rancangan yang baik terlihat jelas; rancangan yang hebat terlihat tak terelakkan.
Prinsip desain, sering dikaitkan dengan tradisi desain industri
Cara Dibaca AI
API untuk konsumen berupa agenKonsumen baru telah muncul: agen AI yang membaca dokumentasi lalu memanggil endpoint Anda atas nama pengguna. Bab ini punya dua sisi. Pertama, bagaimana mendesain API agar mudah dipahami dan dipakai benar oleh model bahasa. Kedua — sesuai permintaan seri buku ini — bagaimana buku ini sendiri dirancang agar terbaca baik oleh AI.
Mengapa AI adalah konsumen kelas satu
Ketika sebuah agen diminta "buatkan task untuk tim desain", ia tidak melihat UI Anda; ia membaca deskripsi API — idealnya spec OpenAPI — lalu menyusun panggilan. Kualitas desain API Anda langsung menentukan seberapa andal agen bekerja. API yang dapat diprediksi, bernama jelas, dan terdokumentasi eksplisit adalah API yang ramah-AI. Untungnya, semua yang membuat API ramah-AI juga membuatnya ramah-manusia.
Tujuh prinsip API ramah-AI
- Deskripsi eksplisit di OpenAPI. Setiap endpoint, parameter, dan field punya
descriptionbermakna. Model membaca deskripsi ini untuk memutuskan kapan & bagaimana memanggil. - Nama yang menjelaskan diri.
dueAtlebih baik darid2.operationId: createTaskmembantu model merujuk aksi dengan tepat. - Enum, bukan teks bebas. Status sebagai enum tertutup mencegah model mengarang nilai seperti
"finished"alih-alih"done". - Error yang dapat ditindaklanjuti.
codestabil +detailjelas memungkinkan agen memperbaiki panggilan sendiri (mis. menambah field wajib yang kurang). - Contoh nyata. Sertakan
examplesdi OpenAPI. Model belajar bentuk yang benar dari contoh jauh lebih cepat daripada dari deskripsi abstrak. - Idempotensi & efek samping jelas. Tandai operasi yang menulis; agen yang tahu sebuah aksi mengubah data akan lebih hati-hati (dan bisa memakai Idempotency-Key saat retry).
- Konsistensi menyeluruh. Bila satu endpoint dipahami, model bisa menggeneralisasi ke yang lain. Ketidakonsistenan memaksa model menebak — sumber kesalahan.
Banyak platform AI mengubah endpoint menjadi "tools" yang bisa dipanggil model. Skema parameter tool sering diturunkan langsung dari OpenAPI/JSON Schema Anda. Artinya: berinvestasi pada spec yang bersih otomatis menghasilkan integrasi AI yang lebih baik — tanpa kerja tambahan.
Bagaimana buku ini dirancang untuk AI
Buku ini — sebagai sebuah dokumen HTML — dibuat agar mudah diurai oleh manusia maupun mesin:
- Struktur semantik. Judul bertingkat (
h1–h4),sectionber-id, tabel sungguhan, dan daftar teratur — bukandivtak bermakna — sehingga parser bisa memetakan hierarki isi. - Anchor stabil. Setiap bab punya
idyang bisa dirujuk (mis.#bab-10), memudahkan pengutipan tepat. - Kode dalam blok jelas. Contoh berada di dalam
<pre>berlabel, bisa disalin utuh tanpa tercampur prosa. - Konsistensi istilah. Istilah kunci didefinisikan sekali di Daftar Istilah & Glosarium, lalu dipakai konsisten — mengurangi ambiguitas.
- Metadata di head.
lang="id",meta description, dantitledeskriptif memberi konteks di tingkat dokumen. - Bahasa yang lugas. Kalimat berstruktur, satu gagasan per paragraf, dan ringkasan di kotak — memudahkan ekstraksi poin.
Bila sebuah model diminta menjawab "bagaimana cara membuat POST idempoten menurut buku ini?", struktur di atas memungkinkannya menemukan Bab 10, mengutip mekanisme Idempotency-Key, dan menyalin contoh kodenya dengan akurat. Desain yang baik untuk mesin adalah desain yang baik, titik.
Program harus ditulis agar dibaca manusia, dan baru kebetulan bisa dijalankan mesin.
Harold Abelson & Gerald Jay Sussman, "Structure and Interpretation of Computer Programs" (1985)
Penutup, FAQ & Glosarium
rangkuman · tanya-jawab · istilahKita telah menempuh perjalanan dari satu request HTTP mentah hingga arsitektur SaaS yang utuh. Sebelum berpisah, mari rangkum inti, jawab pertanyaan yang paling sering muncul, dan bekukan istilah-istilah kunci sebagai rujukan cepat.
Sepuluh inti yang layak diingat
- API adalah kontrak; rancang bentuknya sebelum menulis kode.
- Pakai metode HTTP sesuai maksud dan status code secara jujur.
- Modelkan resource sebagai kata benda; biarkan metode jadi kata kerja.
- Buat error konsisten di semua endpoint dengan
codestabil. - Validasi di batas; jangan pernah percaya input.
- Pisahkan autentikasi (siapa) dari otorisasi per-objek (boleh atau tidak).
- Rancang operasi tulis agar idempoten — jaringan tidak sempurna.
- Pilih paradigma (REST/tRPC/GraphQL) sesuai konsumen, bukan tren.
- Jadikan OpenAPI sumber kebenaran; dokumentasi & tipe mengikuti.
- Konsistensi mengalahkan kepintaran; API terbaik terasa ditulis satu orang.
FAQ
REST atau GraphQL untuk proyek baru?
Mulai dengan REST kecuali Anda punya alasan kuat sebaliknya. REST lebih sederhana, caching HTTP bekerja otomatis, dan tooling matang. Pilih GraphQL bila banyak layar berbeda butuh bentuk data yang sangat beragam dari graf yang saling terhubung, dan Anda siap membayar kompleksitas N+1 serta pembatasan kueri.
Perlukah langsung versioning dari v1?
Ya — beri prefiks /v1 sejak awal. Biayanya nyaris nol dan menyelamatkan Anda saat perubahan merusak tak terelakkan datang. Naikkan versi hanya untuk perubahan yang merusak.
camelCase atau snake_case?
Keduanya baik; yang penting konsisten. Ekosistem JavaScript cenderung camelCase; banyak API publik memilih snake_case. Pilih satu dan tegakkan lewat linter/skema.
Kapan pakai PUT vs PATCH?
PUT mengganti resource utuh (kirim seluruh representasi); PATCH mengubah sebagian. Untuk form edit biasa, PATCH lebih praktis. PUT idempoten secara alami; buat PATCH idempoten bila memungkinkan.
Bagaimana menangani perubahan yang merusak pada konsumen mobile?
Jangan pernah mengubah v1 secara merusak. Terbitkan v2 berdampingan, dukung keduanya selama masa transisi, umumkan jadwal deprecation, dan pantau lalu lintas versi lama sebelum mematikannya.
Haruskah semua endpoint saya realtime?
Tidak. Realtime menambah kompleksitas skala yang nyata. Pakai request/response untuk mayoritas kasus; tambahkan SSE/WebSocket hanya di tempat yang benar-benar butuh push langsung ke pengguna.
Di mana batas antara Server Action dan API sungguhan?
Server Action pas untuk mutasi internal aplikasi Next.js Anda sendiri. Begitu konsumen lain (mobile, mitra, agen AI) perlu memanggilnya, angkat menjadi endpoint REST/tRPC terdokumentasi. Keduanya bisa hidup berdampingan.
Glosarium
| Istilah | Definisi |
|---|---|
| BFF | Backend for Frontend — lapisan penyesuai per jenis klien. |
| BOLA / IDOR | Celah akses objek milik orang lain dengan menebak identitasnya. |
| Cursor pagination | Paginasi berbasis penunjuk posisi, stabil pada data berubah. |
| ETag | Sidik jari representasi untuk caching & concurrency. |
| HATEOAS | Menyertakan tautan aksi berikutnya dalam response. |
| HMAC | Kode otentikasi pesan berbasis hash + secret; dipakai tanda tangan webhook. |
| Idempoten | Operasi yang aman diulang; efek akhirnya sama. |
| JWT | Token mandiri bertanda-tangan yang membawa klaim. |
| N+1 | Ledakan kueri saat memuat relasi satu per satu; diatasi batching. |
| OAuth2 | Kerangka delegasi akses tanpa berbagi password. |
| OpenAPI | Spesifikasi mesin-terbaca untuk API REST (dulu Swagger). |
| Problem Details | Format error HTTP standar (RFC 9457). |
| Rate limiting | Pembatasan jumlah request per identitas per waktu. |
| SSE | Server-Sent Events — aliran push satu arah lewat HTTP. |
| tRPC | Pemanggilan procedure type-safe end-to-end untuk TypeScript. |
| WebSocket | Kanal dua arah persisten antara klien dan server. |
| Webhook | Panggilan HTTP dari server Anda ke URL konsumen saat peristiwa terjadi. |
Penutup
Desain API yang baik jarang dipuji, karena ketika ia bekerja, ia tak terlihat — frontend dan backend menyatu mulus, integrasi berjalan, dan bug yang tak pernah terjadi tak pernah dihitung. Namun justru ketidaktampakan itulah tandanya. Setiap keputusan kecil — satu nama field yang konsisten, satu status code yang jujur, satu error yang bisa dibaca mesin — menumpuk menjadi sistem yang menyenangkan untuk dibangun di atasnya. Rancanglah kontrak Anda dengan hormat kepada semua yang akan memakainya: rekan setim hari ini, diri Anda enam bulan lagi, mitra yang belum Anda kenal, dan kini, agen-agen yang membaca sebelum memanggil. Selamat merancang.
Kesempurnaan tercapai bukan ketika tak ada lagi yang bisa ditambahkan, melainkan ketika tak ada lagi yang bisa dikurangi.
Antoine de Saint-Exupéry, "Terre des Hommes" (1939)
Checklist Rilis API
Gunakan daftar ini sebelum menandai sebuah API "siap produksi". Ia merangkum keputusan lintas bab menjadi satu halaman yang bisa dicetak.
Desain & kontrak
- Resource dinamai kata benda jamak; metode HTTP sesuai maksud.
- Versi di path (
/v1) sudah ada sejak awal. - Penamaan field konsisten (satu gaya), tipe stabil (waktu ISO, uang aman).
- Daftar memakai pagination; ada konvensi filter & sort ber-allowlist.
- Contoh request/response sukses & error ditulis untuk tiap endpoint.
Validasi & error
- Semua input divalidasi di batas dengan skema.
- Format error seragam (Problem Details) dengan
codestabil &request_id. - Status code dipetakan konsisten (400/401/403/404/409/422/429/5xx).
Keamanan
- HTTPS dipaksakan; token berumur pendek + refresh; algoritma JWT diverifikasi.
- Otorisasi per-objek (anti BOLA/IDOR) di setiap endpoint berdata.
- Mass assignment dicegah; hanya field diizinkan yang dipetakan.
- Rahasia di secret manager; CORS dibatasi origin tepercaya.
Ketahanan & skala
- Rate limiting per identitas + header kuota &
Retry-After. - Operasi tulis mendukung
Idempotency-Key; edit memakaiIf-Match. - Caching (
ETag/Cache-Control) untuk endpoint baca yang sering. - Webhook (bila ada) ditandatangani, retry backoff, dedup di penerima.
Dokumentasi & pengujian
- OpenAPI lengkap dengan deskripsi & contoh; dokumentasi publik tersedia.
- Tes integrasi HTTP + contract test terhadap OpenAPI di CI.
- Koleksi curl/Postman untuk smoke test tiap deploy.
- Logging terstruktur dengan
request_id; alarm anomali aktif.
