Kenapa ini penting
API adalah kontrak. Begitu frontend, aplikasi mobile, atau tim lain mulai memakainya, mengubah bentuk URL atau format JSON berarti merusak kode mereka. Desain yang asal-asalan (/getAllTasks, /deleteTaskById?id=5, error kadang string kadang objek) membuat semua orang yang memakainya harus menebak-nebak. Desain yang konsisten membuat API bisa ditebak: kalau kamu tahu cara mengambil tugas, kamu otomatis tahu cara mengambil pengguna.
REST (Representational State Transfer) adalah gaya desain API yang paling umum. Ia bukan library dan bukan standar resmi yang kaku, melainkan sekumpulan kebiasaan yang memanfaatkan HTTP sebagaimana mestinya: URL untuk menunjuk "benda", method untuk menyatakan "aksi", dan status code untuk menyatakan "hasil".
Konsep inti
Resource dan URL
Pikirkan API seperti lemari arsip. Setiap laci adalah koleksi (/tugas, /pengguna), dan setiap map di dalamnya adalah satu item (/tugas/42). URL menunjuk benda, bukan aksi.
| Hindari | Lebih baik |
|---|---|
GET /getTugas |
GET /tugas |
POST /createTugas |
POST /tugas |
POST /tugas/hapus/5 |
DELETE /tugas/5 |
GET /tugasByUser?id=3 |
GET /pengguna/3/tugas |
Aturan praktis: kata benda jamak, huruf kecil, pisahkan kata dengan tanda hubung (/kategori-produk), dan relasi bersarang maksimal satu tingkat (/pengguna/3/tugas, bukan /pengguna/3/proyek/9/tugas/42/komentar).
Method dan maknanya
| Method | Aksi | Contoh | Status sukses | Idempoten? |
|---|---|---|---|---|
| GET | Ambil | GET /tugas, GET /tugas/5 |
200 | Ya |
| POST | Buat baru | POST /tugas |
201 + header Location |
Tidak |
| PUT | Ganti seluruhnya | PUT /tugas/5 |
200 | Ya |
| PATCH | Ubah sebagian | PATCH /tugas/5 dengan { "selesai": true } |
200 | Biasanya |
| DELETE | Hapus | DELETE /tugas/5 |
204 | Ya |
Idempoten artinya mengulang request yang sama berkali-kali menghasilkan keadaan akhir yang sama. Menghapus tugas 5 dua kali tetap berakhir "tugas 5 tidak ada" (request kedua boleh menjawab 404, tapi datanya tidak berubah lagi). Mengirim POST dua kali membuat dua tugas. Ini penting untuk retry otomatis: GET, PUT, DELETE aman diulang, POST tidak.
GET tidak boleh mengubah data. Kalau GET /tugas/5/hapus menghapus data, crawler mesin pencari atau fitur prefetch browser bisa menghapus datamu tanpa sengaja.
Status code yang benar
Selain yang sudah dibahas, dua kode berikut sering dipakai di API:
- 409 Conflict: data bentrok, misalnya email sudah terdaftar.
- 422 Unprocessable Content: JSON-nya valid, tapi isinya melanggar aturan (judul terlalu pendek). Sebagian tim memakai 400 untuk semua kesalahan input. Keduanya umum; yang penting konsisten dan terdokumentasi. Di materi ini: 400 untuk JSON rusak, 422 untuk validasi.
Kelompok besar: 2xx sukses, 3xx redirect, 4xx kesalahan client (client harus memperbaiki request), 5xx kesalahan server (client tidak bisa berbuat apa-apa selain mencoba lagi nanti).
Pagination, filter, dan sort
Jangan pernah mengembalikan seluruh tabel. Bayangkan 100 ribu tugas dikirim sekaligus ke HP pengguna. Gunakan query string:
- Pagination:
?page=2&limit=10. Selalu beri batas maksimum (misalnyalimit≤ 50) dan nilai default. - Filter:
?selesai=true&q=node(q untuk pencarian teks). - Sort:
?sort=judul(naik) atau?sort=-dibuatPada(turun, tanda minus). Hanya izinkan field tertentu.
Sertakan metadata agar client bisa membuat tombol halaman:
{ "data": [ ... ], "meta": { "page": 1, "limit": 10, "total": 37, "totalPages": 4 } }
Pagination model page/limit (offset) mudah dipahami. Untuk data yang sangat besar atau feed yang terus bertambah, banyak API memakai cursor pagination (?after=<id terakhir>), yang dibahas saat kita memakai database.
Versioning
Suatu saat kamu perlu mengubah format secara tidak kompatibel. Supaya client lama tidak rusak, beri versi sejak awal. Cara paling umum dan paling mudah dipahami adalah di URL: /api/v1/tugas. Saat ada perubahan besar, buat /api/v2/... dan biarkan v1 hidup selama masa transisi. Alternatif lain lewat header (Accept: application/vnd.app.v2+json), tapi lebih sulit diuji dengan browser biasa.
Format response dan error yang konsisten
Pilih satu "amplop" dan pakai di semua endpoint. Contoh yang kita pakai:
// sukses
{ "data": { "id": "…", "judul": "Belajar Node" } }
// error
{ "error": { "code": "VALIDATION_ERROR", "message": "Data yang dikirim tidak valid", "details": { } } }
codeadalah string tetap yang dibaca program (frontend bisa menulisif (err.code === "NOT_FOUND")).messageadalah teks untuk manusia, boleh berubah atau diterjemahkan.detailsopsional, misalnya daftar field yang salah.
Jangan pernah mengirim stack trace atau pesan error database ke client. Itu membocorkan struktur internal ke penyerang. Catat di log server, kirim pesan umum ke client.
Nama field dan tanggal
Pilih satu gaya penamaan (camelCase umum di dunia JavaScript) dan pakai konsisten. Kirim tanggal dalam format ISO 8601 UTC (2026-10-23T18:11:43.157Z), biarkan frontend yang mengubahnya ke zona waktu pengguna (WIB, WITA, WIT).
Contoh kode
Rancangan lengkap API Tugas yang akan kita bangun di modul CRUD:
# Dasar URL: /api/v1
GET /api/v1/health # → 200 {"status":"ok"}
GET /api/v1/tugas # → 200 {"data":[...],"meta":{...}}
?page=1&limit=10 # pagination (limit maks 50)
&selesai=true|false # filter status
&q=kata # cari di judul
&sort=dibuatPada|-dibuatPada|judul|-judul
GET /api/v1/tugas/:id # → 200 {"data":{...}} | 404
POST /api/v1/tugas # → 201 {"data":{...}} + Location | 400 | 422
PATCH /api/v1/tugas/:id # → 200 {"data":{...}} | 404 | 422
DELETE /api/v1/tugas/:id # → 204 (tanpa body) | 404
Helper kecil untuk menjaga amplop tetap konsisten:
// src/http.js, helper amplop response
export const ok = (res, data, meta) => res.json(meta ? { data, meta } : { data });
export const created = (res, data, lokasi) => res.status(201).location(lokasi).json({ data });
export const gagal = (res, status, code, message, details) =>
res.status(status).json({ error: { code, message, details } });
// Pemakaian:
// ok(res, tugas) → 200 {"data":{...}}
// ok(res, daftar, { page, limit, total }) → 200 {"data":[...],"meta":{...}}
// created(res, baru, `/api/v1/tugas/${id}`) → 201 + header Location
// gagal(res, 404, "NOT_FOUND", "Tugas tidak ditemukan")
Fungsi pagination murni yang bisa diuji tanpa server:
// paginate.js
export function paginate(items, page, limit) {
const total = items.length;
return {
data: items.slice((page - 1) * limit, page * limit),
meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
};
}
const angka = Array.from({ length: 23 }, (_, i) => i + 1);
console.log(paginate(angka, 3, 10));
// → { data: [ 21, 22, 23 ], meta: { page: 3, limit: 10, total: 23, totalPages: 3 } }
Kesalahan umum
- Kata kerja di URL (
/createTugas,/tugas/delete/5). Biarkan method HTTP yang menyatakan aksi. - GET yang mengubah data. Bisa terpicu oleh crawler, prefetch, atau sekadar refresh. Ubah data hanya lewat POST/PUT/PATCH/DELETE.
- Tidak ada batas
limit. Client (atau penyerang) bisa meminta?limit=1000000dan membuat server kehabisan memori. Selalu beri maksimum. - Format error berubah-ubah: kadang string, kadang
{ msg }, kadang{ error: { ... } }. Frontend jadi penuhifaneh. Tetapkan satu bentuk. - Membocorkan detail internal seperti stack trace, query SQL, atau path file di response error. Catat di log, kirim pesan umum.
Latihan
- Mudah, Rancang URL dan method REST untuk fitur komentar pada tugas: daftar komentar sebuah tugas, tambah komentar, hapus komentar.
- Sedang, Untuk setiap situasi berikut tentukan status code: (a) login dengan password salah, (b) user biasa mencoba menghapus tugas milik orang lain, (c) mendaftar dengan email yang sudah dipakai, (d) server gagal terhubung ke database.
- Menantang, Tulis fungsi
parseSort(teks, fieldDiizinkan)yang mengubah"-dibuatPada"menjadi{ field: "dibuatPada", arah: -1 }dan melempar error kalau field tidak diizinkan. Tambahkan dukungan beberapa field dipisah koma:"selesai,-dibuatPada".
Lihat solusi
1.
GET /api/v1/tugas/:id/komentar # daftar komentar tugas
POST /api/v1/tugas/:id/komentar # tambah komentar → 201
DELETE /api/v1/komentar/:komentarId # hapus → 204 (komentar punya id sendiri, tak perlu bersarang)
2. (a) 401 Unauthorized, (b) 403 Forbidden, (c) 409 Conflict, (d) 500 Internal Server Error (atau 503 Service Unavailable kalau sifatnya sementara).
3.
export function parseSort(teks, fieldDiizinkan) {
return teks.split(",").map((bagian) => {
const arah = bagian.startsWith("-") ? -1 : 1;
const field = bagian.replace(/^-/, "");
if (!fieldDiizinkan.includes(field)) {
throw new Error(`Tidak bisa mengurutkan berdasarkan '${field}'`);
}
return { field, arah };
});
}
console.log(parseSort("selesai,-dibuatPada", ["selesai", "dibuatPada", "judul"]));
// → [ { field: 'selesai', arah: 1 }, { field: 'dibuatPada', arah: -1 } ]
Ringkasan
- REST memakai URL untuk benda (kata benda jamak), method untuk aksi, dan status code untuk hasil.
- GET tidak boleh mengubah data; GET, PUT, dan DELETE idempoten, POST tidak.
- Selalu sediakan pagination dengan batas maksimum, filter, dan sort yang dibatasi field tertentu, plus metadata
total. - Beri versi sejak awal (
/api/v1) agar perubahan besar tidak merusak client lama. - Tetapkan satu format sukses (
{ data }) dan satu format error ({ error: { code, message, details } }), dan jangan bocorkan detail internal.