Apa itu REST?
REST (Representational State Transfer) itu arsitektur style untuk mendesain API di web. Dibuat oleh Roy Fielding di disertasinya tahun 2000.
REST bukan protocol, bukan framework, bukan library. Dia kumpulan aturan tentang gimana client dan server berkomunikasi lewat HTTP.
6 Constraint REST
| Constraint | Artinya |
|---|---|
| Client-Server | Client dan server terpisah, bisa berkembang sendiri-sendiri |
| Stateless | Setiap request harus bawa semua info yang dibutuhkan. Server ga simpan state client |
| Cacheable | Response harus bilang apakah boleh di-cache atau tidak |
| Uniform Interface | Interface yang konsisten dan predictable |
| Layered System | Boleh ada layer di antara client dan server (load balancer, proxy, gateway) |
| Code on Demand | Server bisa kirim executable code ke client (opsional, jarang dipakai) |
Yang paling penting: Stateless dan Uniform Interface.
Resource-Based Thinking
Kunci REST itu pikirkan resource, bukan action.
Resource itu noun, bukan verb. Contoh:
❌ Salah (action-based):
GET /getUsers
POST /createUser
POST /deleteUser/123
GET /getUserOrders/123
✅ Benar (resource-based):
GET /users
POST /users
DELETE /users/123
GET /users/123/orders
Setiap resource punya URI (Uniform Resource Identifier) yang jelas. HTTP method yang menentukan apa yang dilakukan.
HTTP Methods
| Method | Fungsi | Idempotent? | Safe? |
|---|---|---|---|
| GET | Ambil data | ✅ | ✅ |
| POST | Buat resource baru | ❌ | ❌ |
| PUT | Update keseluruhan resource | ✅ | ❌ |
| PATCH | Update sebagian resource | ❌ | ❌ |
| DELETE | Hapus resource | ✅ | ❌ |
Idempotent: kalau dipanggil berkali-kali hasilnya sama. DELETE /users/123 — panggil 1x atau 5x, user 123 tetap terhapus.
Safe: ga mengubah state server. GET cuma baca, ga ubah apa-apa.
PUT vs PATCH
// User saat ini
{
"id": 123,
"name": "Budi",
"email": "budi@mail.com",
"phone": "081234567890"
}
PUT — replace seluruh resource. Harus kirim semua field:
PUT /users/123
{
"name": "Budi Santoso",
"email": "budi@mail.com",
"phone": "081234567890"
}
// Kalau ga kirim "phone", phone jadi null!
PATCH — update sebagian field aja:
PATCH /users/123
{
"name": "Budi Santoso"
}
// Field lain tetap tidak berubah
URL Naming Convention
Rules
- Gunakan noun, bukan verb
✅ /users
❌ /getUsers
- Gunakan plural
✅ /users, /products, /orders
❌ /user, /product, /order
- Gunakan kebab-case
✅ /user-profiles
❌ /userProfiles, /user_profiles
- Hierarchy pakai nesting
✅ /users/123/orders (orders milik user 123)
✅ /users/123/orders/456 (order 456 milik user 123)
❌ /getUserOrders?userId=123
- Jangan terlalu dalam nesting-nya (max 2-3 level)
✅ /users/123/orders
❌ /users/123/orders/456/items/789/reviews/101
// Better: /order-items/789/reviews
- Trailing slash konsisten (pilih salah satu)
✅ /users (tanpa trailing slash - umumnya lebih dipakai)
✅ /users/ (dengan trailing slash - konsisten)
❌ kadang /users kadang /users/
HTTP Status Codes
Jangan cuma return 200 untuk semuanya. Status code itu bagian dari API contract.
2xx — Success
| Code | Nama | Kapan Pakai |
|---|---|---|
| 200 | OK | GET berhasil, atau general success |
| 201 | Created | POST berhasil buat resource baru |
| 204 | No Content | DELETE berhasil, ga ada body response |
3xx — Redirection
| Code | Nama | Kapan Pakai |
|---|---|---|
| 301 | Moved Permanently | Resource pindah URL permanen |
| 304 | Not Modified | Cache masih valid |
4xx — Client Error
| Code | Nama | Kapan Pakai |
|---|---|---|
| 400 | Bad Request | Request body invalid, validation gagal |
| 401 | Unauthorized | Belum login / token expired |
| 403 | Forbidden | Sudah login tapi ga punya akses |
| 404 | Not Found | Resource ga ada |
| 409 | Conflict | Duplikat, atau state conflict |
| 422 | Unprocessable Entity | Syntax benar tapi semantik salah |
| 429 | Too Many Requests | Rate limit exceeded |
5xx — Server Error
| Code | Nama | Kapan Pakai |
|---|---|---|
| 500 | Internal Server Error | Bug di server |
| 502 | Bad Gateway | Upstream server error |
| 503 | Service Unavailable | Server overload atau maintenance |
401 vs 403
Ini sering ketuker:
- 401 Unauthorized: "Siapa kamu? Gue ga kenal." (belum autentikasi)
- 403 Forbidden: "Gue kenal kamu, tapi kamu ga boleh akses ini." (sudah autentikasi, tapi ga diotorisasi)
Error Handling
Jangan cuma return status code. Kasih error response yang berguna.
Struktur Error yang Baik
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request body tidak valid",
"details": [
{
"field": "email",
"message": "Format email tidak valid",
"value": "bukan-email"
},
{
"field": "age",
"message": "Harus lebih dari 0",
"value": -5
}
]
}
}
Yang Harus Ada di Error Response
- Error code — machine-readable (untuk conditional handling di frontend)
- Message — human-readable (bisa ditampilin ke user)
- Details — per-field validation errors
- Timestamp — kapan error terjadi (optional, bagus buat debugging)
Yang Jangan Ada
- Stack trace di production ❌
- Internal database error message ❌
- Sensitive info (password, token, SQL query) ❌
// ❌ JANGAN GINI
{
"error": "SQL Error: SELECT * FROM users WHERE email='x'; DROP TABLE users;--"
}
// ✅ GINI
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Terjadi kesalahan server. Silakan coba lagi."
}
}
Pagination
Ga mungkin return 1 juta rows di satu response. Harus di-page.
Offset-Based Pagination
GET /products?page=2&limit=20
Response:
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"totalItems": 1543,
"totalPages": 78
}
}
Pro: simple, bisa jump ke page mana aja
Kontra: kalau data berubah di tengah (insert/delete), bisa skip atau duplikat item
Cursor-Based Pagination
GET /products?limit=20&cursor=eyJpZCI6MTAwfQ==
Response:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MTIwfQ==",
"hasMore": true
}
}
Cursor itu encoded reference ke item terakhir. Biasanya Base64 dari {id: 100}.
Pro: consistent results walaupun data berubah, performance lebih baik di dataset besar
Kontra: ga bisa jump ke page 50 langsung. Harus sequential.
Kapan Pakai Mana?
| Kebutuhan | Offset | Cursor |
|---|---|---|
| Jump ke halaman tertentu | ✅ | ❌ |
| Data sering berubah (real-time feed) | ❌ | ✅ |
| Dataset sangat besar | ❌ (slow) | ✅ (fast) |
| Simple implementation | ✅ | ❌ |
Filtering, Sorting, dan Searching
Filtering
Pakai query parameters:
GET /products?category=electronics&minPrice=100000&maxPrice=500000
GET /users?status=active&role=admin
Sorting
GET /products?sort=price // ascending (default)
GET /products?sort=-price // descending (dash prefix)
GET /products?sort=category,-price // multi-sort
Searching
GET /products?search=laptop gaming
GET /users?q=budi
Sparse Fields (Field Selection)
Kalau cuma butuh beberapa field:
GET /users?fields=id,name,email
Response cuma isinya 3 field itu. Hemat bandwidth.
API Versioning
API pasti berubah. Gimana caranya supaya client lama ga tiba-tiba rusak?
Strategy 1: URL Path (Paling Umum)
GET /api/v1/users
GET /api/v2/users
Pro: paling explicit, mudah dipahami
Kontra: URL berubah, bisa merusak caching
Strategy 2: Header
GET /api/users
Accept: application/vnd.myapp.v2+json
Pro: URL tetap bersih
Kontra: kurang discoverable, debugging lebih susah
Strategy 3: Query Parameter
GET /api/users?version=2
Pro: simple
Kontra: optional parameter bisa di-cache salah
Rekomendasi
Pakai URL Path versioning (/api/v1/). Paling clear, paling banyak dipakai, paling gampang di-debug.
Aturan versioning:
- Naikkan major version cuma kalau ada breaking change
- Backward-compatible changes ga perlu new version
- Support minimal 2 versi terakhir
- Kasih deprecation notice sebelum matiin versi lama
Response Format
Envelope Pattern
Bungkus response dalam wrapper yang konsisten:
// Success
{
"status": "success",
"data": {
"id": 123,
"name": "Budi"
}
}
// Success (list)
{
"status": "success",
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"totalItems": 100
}
}
// Error
{
"status": "error",
"error": {
"code": "NOT_FOUND",
"message": "User tidak ditemukan"
}
}
Pro: frontend selalu tahu struktur response. Cek status dulu baru handle.
Kontra: sedikit redundant sama HTTP status code.
Naming Convention di JSON
Pakai camelCase (paling umum di JavaScript ecosystem):
{
"userId": 123,
"firstName": "Budi",
"createdAt": "2026-03-07T10:00:00Z"
}
Jangan mix:
// ❌ Jangan gini
{
"user_id": 123,
"firstName": "Budi",
"Created-At": "..."
}
Timestamp Format
Selalu pakai ISO 8601 dan UTC:
{
"createdAt": "2026-03-07T10:00:00Z",
"updatedAt": "2026-03-07T15:30:00Z"
}
Jangan "07 Mar 2026" atau UNIX timestamp 1741334400.
Authentication
Token-Based (JWT)
GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
- Client login → dapet token
- Setiap request kirim token di
Authorizationheader - Server verifikasi token
API Key
GET /api/products
X-API-Key: sk_live_abc123xyz
- Biasanya untuk server-to-server communication
- Simple, tapi kurang secure kalau expose ke client
Jangan Pernah
- Taruh credentials di URL:
GET /api/users?token=abc123❌ (keliatan di logs, browser history) - Pakai HTTP (bukan HTTPS) untuk kirim token ❌
Rate Limiting
Lindungi API dari abuse dan overload.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1741334400
Headers yang harus ada:
| Header | Artinya |
|---|---|
X-RateLimit-Limit |
Max requests per window |
X-RateLimit-Remaining |
Sisa request yang bisa dilakukan |
X-RateLimit-Reset |
Kapan window reset (UNIX timestamp) |
Retry-After |
Berapa detik harus nunggu |
Strategi umum:
- 100 requests per menit untuk public API
- 1000 requests per menit untuk authenticated users
- Lebih tinggi untuk partner/premium tier
HATEOAS (Bonus)
HATEOAS (Hypermedia As The Engine Of Application State) — level tertinggi REST maturity.
Response berisi link ke action selanjutnya:
{
"data": {
"id": 123,
"name": "Budi",
"status": "active"
},
"_links": {
"self": { "href": "/api/v1/users/123" },
"orders": { "href": "/api/v1/users/123/orders" },
"deactivate": { "href": "/api/v1/users/123/deactivate", "method": "POST" }
}
}
Client ga perlu hardcode URL. Tinggal ikutin link dari response. Keren secara teori, tapi jarang diimplementasi penuh di real world karena complexity-nya.
Checklist: API Design Review
Sebelum release API, cek:
- URL pakai noun, plural, kebab-case?
- HTTP method sesuai (GET baca, POST buat, PUT update, DELETE hapus)?
- Status code benar (bukan cuma 200 untuk semuanya)?
- Error response ada code, message, dan details?
- Pagination ada di semua list endpoint?
- Sensitive data ga bocor di error response?
- Versioning sudah diterapkan?
- Rate limiting sudah dipasang?
- Auth pakai header (bukan URL parameter)?
- Timestamp pakai ISO 8601 UTC?
- Response format konsisten di semua endpoint?
- HTTPS only?
Ringkasan
| Konsep | Penjelasan |
|---|---|
| REST | Arsitektur style berbasis resource + HTTP |
| Resource | Noun-based URL (/users, /orders) |
| HTTP Methods | GET (baca), POST (buat), PUT/PATCH (update), DELETE (hapus) |
| Status Codes | 2xx sukses, 4xx client error, 5xx server error |
| Pagination | Offset-based (simple) vs Cursor-based (scalable) |
| Versioning | /api/v1/ paling umum dan recommended |
| Error Handling | Structured error dengan code + message + details |
| Rate Limiting | Protect API dari abuse dengan request quota |
| HATEOAS | Response berisi links ke related actions |
Kesimpulan
REST API yang bagus itu predictable. Developer yang baru pertama kali baca dokumentasi API kamu harus bisa nebak gimana endpoint lain bekerja hanya dari melihat satu contoh.
Yang perlu diingat:
- Think in resources — noun, bukan verb
- HTTP methods punya makna — jangan POST untuk semuanya
- Status codes bukan hiasan — 201 Created, bukan 200 OK untuk POST
- Error response yang berguna — jangan cuma "error occurred"
- Konsistensi itu segalanya — naming, format, structure harus seragam
- Versioning dari hari pertama — ga ada API yang ga berubah
- Pagination wajib — kalau ada list endpoint, harus paginasi
API itu kontrak antara frontend dan backend. Kalau kontraknya jelas, kedua tim bisa kerja paralel tanpa drama. Kalau ga, siap-siap meeting tiap hari 😅
