Programming

Mengenal REST API: Pengertian, Prinsip, dan Cara Kerjanya

M
MUGHU
37 menit baca
Mengenal REST API: Pengertian, Prinsip, dan Cara Kerjanya
Daftar isi

REST API itu seperti pelayan di restoran. Kamu duduk sebagai tamu (aplikasi), dapur sebagai server, dan kamu nggak mungkin nyelonong masuk dapur buat masak sendiri. Pelayan yang mondar-mandir bawa pesanan dan balik bawa makanan—itulah peran API. Analogi ini kepakai di banyak penjelasan karena memang paling gampang ditangkap.

Artikel ini bahas REST API dari nol sampai kamu bisa bikin dan memanggilnya sendiri: pengertian, prinsip, cara kerja, contoh kode beserta output, error yang sering muncul, sampai perbandingan REST vs RESTful.

Ringkasan singkat (buat yang buru-buru)

REST API adalah gaya arsitektur untuk pertukaran data antar aplikasi lewat protokol HTTP, memakai metode standar GET, POST, PUT, DELETE dan format data ringan seperti JSON. Sifatnya stateless, jadi tiap permintaan berdiri sendiri tanpa server perlu mengingat sesi sebelumnya.

Poin penting yang akan kita bahas:

  • Beda API dan REST API

  • 6 prinsip (constraints) arsitektur REST

  • Cara kerja request–response, lengkap dengan status code

  • Praktik bikin REST API sederhana + cara memanggilnya

  • Error umum dan cara benerinnya

  • Beda REST API vs RESTful API

Mulai dari yang paling dasar: apa itu API?

API singkatan dari Application Programming Interface. Sederhananya, API adalah perantara perangkat lunak yang bikin dua aplikasi berbeda bisa "ngobrol" dan tukar data. Dia nerima permintaan dari satu aplikasi, nerusin ke sistem lain, lalu balik bawa jawaban.

Tanpa API, tiap aplikasi harus paham detail cara kerja sistem lain—struktur datanya, mekanisme komunikasinya. Ribet, nggak efisien, susah dirawat. Dengan API, komunikasinya jadi terstruktur dan standar.

Bayangin aplikasi ojek online. Dia harus nampilin peta (Google Maps), proses pembayaran (payment gateway), dan kirim notifikasi. Tiga sistem beda perusahaan, beda bahasa pemrograman, tapi tetap nyambung. Yang jembatani itu API.

Lalu, apa itu REST API?

REST singkatan dari Representational State Transfer. Konsep ini diperkenalkan Roy Fielding tahun 2000 lewat disertasi doktoralnya. Dia menetapkan sekumpulan batasan arsitektur. Kalau sebuah API ngikutin aturan-aturan ini, dia disebut RESTful API.

Kalau API itu pelayannya, REST adalah SOP-nya—aturan main gimana pelayan itu harus kerja.

REST API jalan di atas HTTP, protokol yang sama yang dipakai browser buat buka halaman web. Kelebihan utamanya: universal. Server kamu mau ditulis pakai Python, PHP, atau Java, dan klien kamu mau Android, iOS, atau web—selama sama-sama ngerti HTTP, mereka bisa tukar data. Itu yang bikin REST jadi standar industri.

6 prinsip dasar arsitektur REST

Biar sebuah sistem layak disebut RESTful, dia harus patuh beberapa batasan. Tujuannya satu: bikin sistem yang cepat, andal, dan gampang dibesarkan.

1. Client-Server

Ada pemisahan jelas antara klien (frontend yang dilihat pengguna) dan server (backend yang ngurus data). Untungnya, tim frontend bisa poles tampilan tanpa ganggu logika database di backend, dan sebaliknya. Keduanya berkembang sendiri-sendiri selama antarmuka komunikasinya tetap.

2. Stateless

Ini karakter paling krusial. Server nggak nyimpen status sesi klien di antara permintaan. Tiap request harus lengkap—bawa semua info yang server butuhkan buat memprosesnya. Server memperlakukan tiap request seperti tamu baru.

Kenapa ini bagus? Server jadi ringan karena nggak perlu ngingat ribuan sesi. Kalau satu server mati, request bisa dialihkan ke server lain tanpa masalah karena nggak ada data sesi yang hilang. Inilah kunci utama skalabilitas REST.

3. Cacheable

Respons dari server boleh ditandai sebagai bisa disimpan sementara (cache) atau tidak. Misalnya server bilang "daftar produk ini cacheable selama 1 jam". Kalau pengguna buka lagi 10 menit kemudian, aplikasi tinggal ambil dari memori, nggak perlu nanya server lagi. Aplikasi terasa jauh lebih cepat.

4. Uniform Interface

Harus ada "bahasa" yang seragam. Tiap resource (sumber daya) punya identitas unik berupa URL/URI, dan cara manipulasinya konsisten. Mau aplikasi apa pun, pola pemanggilan datanya sama.

5. Layered System

Klien nggak perlu tahu dia nyambung langsung ke server database, atau lewat load balancer, atau lewat firewall dulu. Arsitektur bisa disusun berlapis. Kamu bisa nambah lapisan keamanan atau caching di depan server utama tanpa klien sadar. Bagus buat keamanan dan skalabilitas.

6. Code on Demand (opsional)

Jarang dipakai. Server boleh ngirim kode yang bisa dieksekusi (misal JavaScript) ke klien buat nambah fungsi sementara.

Cara kerja REST API: bedah request dan response

Alurnya logis karena ngikutin cara kerja web itu sendiri.

Bagian Request (permintaan)

Saat klien mau "ngomong" ke server, dia nyusun paket yang isinya:

1. Endpoint (URL). Alamat tujuan. Contoh: https://api.co.id/v1/produk/123

  • https://api.co.id → base URL

  • /v1/ → versi API (penting biar perubahan ke depan nggak ngerusak aplikasi lama)

  • /produk/ → resource yang diakses

  • 123 → ID spesifik produk

2. HTTP Method (kata kerja). Ngasih tahu server tindakan apa yang diminta:

Method

Fungsi

Operasi CRUD

GET

Ambil/baca data

Read

POST

Kirim/buat data baru

Create

PUT

Perbarui seluruh data

Update

PATCH

Perbarui sebagian data

Update

DELETE

Hapus data

Delete

3. Headers. Metadata tambahan. Contoh: Authorization buat kirim token akses, dan Content-Type: application/json buat ngasih tahu format data.

4. Body. Isi data yang dikirim, dipakai di POST atau PUT, biasanya format JSON.

Bagian Response (jawaban)

1. Status Code — kode tiga digit yang nunjukin hasil:

Kategori

Kode

Arti

2xx Sukses

200 OK

Berhasil

201 Created

Data baru berhasil dibuat

4xx Error klien

400 Bad Request

Format data salah

401 Unauthorized

Nggak punya izin/token

404 Not Found

Data/URL nggak ketemu

5xx Error server

500 Internal Server Error

Server bermasalah

2. Body Response — data yang diminta, biasanya JSON:

JSON
{
  "id": 123,
  "nama_produk": "Sepatu Lari",
  "harga": 500000,
  "stok": 15
}

Simulasi alur lengkap

Misalnya kamu cari tiket pesawat di aplikasi travel:

  1. Klien nyusun request: tekan "Cari Tiket" → aplikasi bikin GET https://api.travel.com/flights?date=2025-12-25&dest=Bali dengan header berisi API key.

  2. Transmisi: request lewat firewall dan load balancer dulu (berkat layered system) sebelum sampai server utama.

  3. Server memproses: cek header ("API key valid?"), baca URL ("minta penerbangan ke Bali"), lalu query ke database.

  4. Server kirim respons: bungkus data jadi JSON, tambah status 200 OK, kirim balik.

  5. Klien nampilin data: aplikasi baca JSON, render daftar penerbangan di layar.

Getting Started: prasyarat sebelum praktik

Sebelum bikin REST API sendiri, siapkan ini:

  • Node.js terpasang (cek dengan node -v). Kita pakai contoh JavaScript karena paling umum.

  • Editor kode (VS Code atau sejenisnya).

  • Alat buat tes API: cURL (sudah ada di terminal) atau Postman.

  • Paham dasar JSON dan terminal.

Kenapa Node.js? Karena ringan buat contoh dan ekosistemnya luas. Tapi konsepnya sama persis kalau kamu pakai Python (Flask/FastAPI), PHP, atau Go.

Praktik: bikin REST API sederhana langkah demi langkah

Kita bikin API daftar produk pakai modul http bawaan Node.js—tanpa nambah dependency. Ponytail: ini sengaja minimalis tanpa framework; naik ke Express kalau endpoint sudah banyak.

Step 1 — Siapkan file dan data

Bikin file server.js:

JAVASCRIPT
const http = require("http");

// Data sementara di memori (pengganti database buat contoh)
let produk = [
  { id: 1, nama: "Sepatu Lari", harga: 500000 },
  { id: 2, nama: "Kaos Olahraga", harga: 120000 },
];

Kenapa penting: memisahkan data dari logika bikin kode gampang dibaca. Di dunia nyata, array ini diganti query database.

Step 2 — Bikin server dan tangani GET

JAVASCRIPT
const server = http.createServer((req, res) => {
  res.setHeader("Content-Type", "application/json");

  // GET /produk → ambil semua produk
  if (req.method === "GET" && req.url === "/produk") {
    res.statusCode = 200;
    res.end(JSON.stringify(produk));
    return;
  }

  // Kalau rute nggak dikenal
  res.statusCode = 404;
  res.end(JSON.stringify({ error: "Not Found" }));
});

server.listen(3000, () => console.log("Server jalan di http://localhost:3000"));

Kenapa penting: Content-Type: application/json ngasih tahu klien format jawabannya. Status 404 dengan pesan jelas bantu developer klien tahu apa yang salah.

Jalankan:

BASH
node server.js

Output yang diharapkan:

CODE
Server jalan di http://localhost:3000

Step 3 — Tes endpoint GET

Di terminal lain:

BASH
curl http://localhost:3000/produk

Output yang diharapkan:

JSON
[{"id":1,"nama":"Sepatu Lari","harga":500000},{"id":2,"nama":"Kaos Olahraga","harga":120000}]

Step 4 — Tambah POST untuk buat data baru

Sisipkan blok ini sebelum bagian 404:

JAVASCRIPT
  // POST /produk → tambah produk baru
  if (req.method === "POST" && req.url === "/produk") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", () => {
      try {
        const data = JSON.parse(body);
        // Validasi di batas kepercayaan — jangan diskip
        if (!data.nama || typeof data.harga !== "number") {
          res.statusCode = 400;
          res.end(JSON.stringify({ error: "nama (string) dan harga (number) wajib" }));
          return;
        }
        const baru = { id: produk.length + 1, nama: data.nama, harga: data.harga };
        produk.push(baru);
        res.statusCode = 201;
        res.end(JSON.stringify(baru));
      } catch {
        res.statusCode = 400;
        res.end(JSON.stringify({ error: "JSON tidak valid" }));
      }
    });
    return;
  }

Kenapa penting: validasi input di titik masuk itu wajib, bukan opsional. Tanpa itu, data sampah masuk dan bisa bikin error beruntun. Status 201 Created adalah standar buat data baru, beda dari 200.

Step 5 — Tes POST

BASH
curl -X POST http://localhost:3000/produk \
  -H "Content-Type: application/json" \
  -d '{"nama":"Topi","harga":75000}'

Output yang diharapkan:

JSON
{"id":3,"nama":"Topi","harga":75000}

Step 6 — Self-check (satu pengecekan yang bisa dijalankan)

Bikin check.js buat mastiin alur dasar jalan:

JAVASCRIPT
const http = require("http");
const assert = require("assert");

http.get("http://localhost:3000/produk", (res) => {
  let body = "";
  res.on("data", (c) => (body += c));
  res.on("end", () => {
    assert.strictEqual(res.statusCode, 200);
    assert.ok(Array.isArray(JSON.parse(body)));
    console.log("OK: GET /produk balik array dengan status 200");
  });
});

Jalankan saat server hidup: node check.js → harus print OK: ....

→ skipped: database, auth, PUT/DELETE. add when: data perlu permanen atau endpoint dipakai lebih dari satu klien.

Error umum dan cara benerinnya

Gejala

Penyebab

Solusi

EADDRINUSE: address already in use :::3000

Port 3000 sudah dipakai proses lain

Matikan proses lama atau ganti port di server.listen

Respons kosong / Unexpected end of JSON input

Body request kosong saat JSON.parse

Tangani dengan try/catch (sudah ada di Step 4)

400 Bad Request terus

Lupa header Content-Type: application/json

Tambahkan header di request klien

401 Unauthorized

Token salah/kedaluwarsa

Cek dan perbarui token di header Authorization

404 Not Found

Salah ketik endpoint atau salah method

Pastikan URL dan HTTP method cocok

CORS error di browser

Server belum izinkan origin klien

Set header Access-Control-Allow-Origin

Data hilang setelah restart

Data cuma di memori

Pindahkan ke database (langkah lanjutan)

Tips troubleshooting: kalau bingung di mana macetnya, console.log(req.method, req.url) di awal handler. Sepele tapi sering langsung ketahuan masalahnya.

REST API vs RESTful API: apa bedanya?

Dua istilah ini sering dipakai gantian, dan itu nggak sepenuhnya salah. Tapi secara teknis ada beda.

  • REST adalah gaya arsitektur (kumpulan prinsip).

  • RESTful API adalah API yang benar-benar ngikutin semua prinsip itu dengan benar.

Jadi: semua RESTful API adalah REST API, tapi nggak semua REST API itu RESTful. Kadang developer cuma pakai sebagian prinsip REST—tetap boleh disebut REST API, tapi belum sepenuhnya RESTful.

Aspek

REST API

RESTful API

Kepatuhan prinsip REST

Sebagian

Ketat dan penuh

Fleksibilitas

Lebih longgar

Lebih terstandar

State management

Belum tentu stateless

Selalu stateless

Pemisahan klien-server

Bisa nggak penuh

Selalu terpisah

Cocok untuk

Proyek umum yang fleksibel

Sistem kompleks berskala besar

REST dibanding alternatif lain

Buat gambaran kapan REST cocok dan kapan nggak:

Kriteria

REST

SOAP

GraphQL

Format data

JSON (umumnya)

XML

JSON

Kurva belajar

Landai

Curam

Sedang

Fleksibilitas ambil data

Tetap per endpoint

Kaku

Klien pilih field sendiri

Over/under-fetching

Bisa terjadi

Bisa terjadi

Diminimalkan

Cocok untuk

Web & mobile umum

Sistem enterprise/perbankan lama

Data kompleks & relasi banyak

REST menang jadi standar de facto karena JSON lebih ringan dari XML-nya SOAP, dan sifat stateless-nya bikin gampang di-scale dari 100 ke jutaan pengguna tinggal nambah server.

Contoh penerapan di dunia nyata

  • Transportasi online: Gojek/Grab ambil lokasi driver, estimasi harga, kirim notifikasi lewat REST API.

  • Pembayaran: aplikasi e-commerce ngobrol dengan API Midtrans/DOKU buat proses transaksi.

  • Media sosial: upload foto = POST ke server, scroll feed = GET.

  • IoT: termostat pintar terima perintah ubah suhu dari aplikasi lewat REST API.

  • Cuaca: aplikasi cuaca ambil suhu terkini dari API publik pakai GET + API key.

Kelebihan dan kekurangan REST API

Kelebihan:

  • Sederhana dan mudah dibaca — pakai metode HTTP standar dan URI yang manusiawi, gampang di-debug.

  • Skalabel — berkat stateless, request gampang disebar ke banyak server.

  • Fleksibel — dukung banyak format, JSON paling populer karena ringan.

  • Agnostik bahasa — backend bisa ganti dari Node.js ke Go tanpa ubah aplikasi Android/iOS, asal struktur JSON tetap.

Kekurangan:

  • Over-fetching & under-fetching — kadang data kebanyakan (kamu cuma butuh nama, dapat seluruh biodata) atau kurang sehingga harus request berkali-kali. Ini yang melahirkan GraphQL.

  • Keamanan butuh lapisan tambahan — karena berbasis HTTP, REST sangat bergantung pada HTTPS (enkripsi SSL) dan autentikasi kuat seperti OAuth 2.0 biar data nggak dicuri di tengah jalan.

Cocok buat siapa, sebaiknya dihindari siapa

  • Cocok buat: developer yang bikin aplikasi web/mobile, tim yang butuh integrasi cepat antar sistem beda teknologi, dan proyek yang perlu skala besar.

  • Sebaiknya pilih lain kalau: kamu butuh ambil data yang sangat kompleks dan saling berelasi (pertimbangkan GraphQL), atau kerja di sistem enterprise lama yang masih ketat pakai SOAP.

Praktik terbaik biar API-mu rapi

  • Pakai kata benda untuk resource: /users, bukan /getAllUsers. Aksi diwakili HTTP method.

  • Pakai versi di URL: /api/v2/users, biar klien lama nggak rusak saat ada perubahan.

  • Tangani error dengan jelas: pesan yang berguna + status code yang tepat.

  • Amankan: selalu pakai TLS/SSL, autentikasi (OAuth 2.0 atau API key), dan otorisasi.

  • Paginasi, filter, sorting buat endpoint yang balikin banyak data, biar transfer datanya hemat.

Penutup

REST API itu jembatan yang nyambungin pulau-pulau teknologi di internet. Begitu kamu paham tiga hal inti—sifat stateless, penggunaan metode HTTP yang bermakna, dan struktur JSON yang ringan—kamu sudah pegang kunci dasar komunikasi data modern.

Langkah selanjutnya: ganti array di memori dengan database sungguhan (PostgreSQL/MongoDB), tambahkan endpoint PUT dan DELETE, lalu pasang autentikasi token. Mulai dari API kecil yang jalan dulu, baru ditingkatkan—belajar sambil ngoding selalu lebih nempel daripada cuma baca teori.


Kalau kamu mau, aku bisa lanjutkan versi yang lebih dalam pada satu bagian tertentu, misalnya implementasi PUT/DELETE lengkap, atau menambah autentikasi JWT. Sebutkan bagian mana.

Lanjut praktik: implementasi PUT dan DELETE

Sampai sini API kita baru bisa baca (GET) dan bikin (POST). Dua operasi CRUD lagi belum: update (PUT) dan hapus (DELETE). Kita tambahin sekarang, tetap pakai modul http bawaan biar nggak nambah dependency.

Masalah pertama yang muncul: rute kita sekarang bukan cuma /produk, tapi /produk/1, /produk/2, dan seterusnya. Artinya kita perlu baca ID dari URL. Caranya gampang—pecah string URL-nya.

Step 7 — Baca ID dari URL

Tambahkan helper kecil di atas createServer:

JAVASCRIPT
// Ambil ID dari URL macam "/produk/3" → 3
function ambilId(url) {
  const bagian = url.split("/"); // ["", "produk", "3"]
  return Number(bagian[2]);
}

Sengaja pakai split biasa, bukan regex atau router. Ponytail: kalau pola URL makin rumit (nested resource macam /produk/3/review/8), baru pindah ke router beneran.

→ skipped: pattern matching rumit, add when: resource mulai bersarang.

Step 8 — Tangani PUT (update penuh)

Sisipkan blok ini sebelum bagian 404:

JAVASCRIPT
  // PUT /produk/:id → ganti seluruh data produk
  if (req.method === "PUT" && req.url.startsWith("/produk/")) {
    const id = ambilId(req.url);
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", () => {
      try {
        const data = JSON.parse(body);
        // Validasi di batas kepercayaan — sama ketatnya dengan POST
        if (!data.nama || typeof data.harga !== "number") {
          res.statusCode = 400;
          res.end(JSON.stringify({ error: "nama (string) dan harga (number) wajib" }));
          return;
        }
        const index = produk.findIndex((p) => p.id === id);
        if (index === -1) {
          res.statusCode = 404;
          res.end(JSON.stringify({ error: "Produk tidak ditemukan" }));
          return;
        }
        produk[index] = { id, nama: data.nama, harga: data.harga };
        res.statusCode = 200;
        res.end(JSON.stringify(produk[index]));
      } catch {
        res.statusCode = 400;
        res.end(JSON.stringify({ error: "JSON tidak valid" }));
      }
    });
    return;
  }

Perhatikan logikanya: PUT itu ganti seluruh data. Jadi kalau klien cuma kirim nama tanpa harga, kita tolak. Ini bedanya sama PATCH, yang boleh kirim sebagian. Banyak yang ketuker di sini, padahal konsekuensinya nyata—pakai PUT dengan data setengah-setengah bisa bikin field lain kehapus tanpa sengaja.

Kenapa validasinya diulang lengkap, bukan cuma dicek seadanya? Karena tiap titik masuk data adalah batas kepercayaan. Klien bisa kirim apa saja, dan tugas server adalah nggak percaya gitu aja. Validasi yang konsisten di POST dan PUT bikin perilaku API gampang ditebak.

Step 9 — Tangani DELETE

JAVASCRIPT
  // DELETE /produk/:id → hapus produk
  if (req.method === "DELETE" && req.url.startsWith("/produk/")) {
    const id = ambilId(req.url);
    const index = produk.findIndex((p) => p.id === id);
    if (index === -1) {
      res.statusCode = 404;
      res.end(JSON.stringify({ error: "Produk tidak ditemukan" }));
      return;
    }
    produk.splice(index, 1);
    res.statusCode = 204; // No Content — sukses tapi nggak ada body
    res.end();
    return;
  }

Ada detail kecil yang sering kelewat: status 204 No Content. Begitu data berhasil dihapus, nggak ada lagi yang perlu dikembalikan, jadi kita kirim 204 tanpa body. Sebenarnya boleh juga pakai 200 dengan pesan konfirmasi, tergantung selera tim. Yang penting konsisten. Kalau kamu pilih 200 di satu endpoint dan 204 di endpoint lain untuk operasi yang sama, klien kamu bakal bingung.

Step 10 — Tes PUT dan DELETE

BASH
# Update produk id 1
curl -X PUT http://localhost:3000/produk/1 \
  -H "Content-Type: application/json" \
  -d '{"nama":"Sepatu Lari Pro","harga":750000}'

Output yang diharapkan:

JSON
{"id":1,"nama":"Sepatu Lari Pro","harga":750000}
BASH
# Hapus produk id 2
curl -i -X DELETE http://localhost:3000/produk/2

Output yang diharapkan (baris pertamanya):

CODE
HTTP/1.1 204 No Content

Pakai flag -i di cURL biar status code-nya keliatan. Soalnya 204 nggak punya body, jadi kalau nggak pakai -i, terminal kamu bakal kosong dan kamu malah ngira ada yang error.

Sekarang API kita sudah lengkap empat operasi CRUD pakai nol dependency tambahan. Buat aplikasi kecil, ini sudah cukup. Tapi begitu kebutuhan naik, ada titik di mana kamu bakal capek nulis if (req.method === ...) berulang-ulang. Di situlah framework masuk.

Kapan sebaiknya naik ke Express

Pertanyaan jujurnya: perlu nggak sih pakai framework? Jawabannya tergantung. Selama endpoint kamu cuma segelintir, modul http bawaan udah oke—nggak ada salahnya hemat. Tapi begitu rute mulai banyak, parsing body berulang, dan kamu butuh middleware (logging, autentikasi, CORS), nulis semuanya manual jadi mubazir. Express bikin semua itu jadi beberapa baris.

Lihat aja bedanya. Versi http murni buat ambil semua produk butuh pengecekan method dan URL manual. Di Express:

JAVASCRIPT
const express = require("express");
const app = express();
app.use(express.json()); // auto-parse body JSON

let produk = [
  { id: 1, nama: "Sepatu Lari", harga: 500000 },
  { id: 2, nama: "Kaos Olahraga", harga: 120000 },
];

app.get("/produk", (req, res) => res.json(produk));

app.post("/produk", (req, res) => {
  const { nama, harga } = req.body;
  if (!nama || typeof harga !== "number") {
    return res.status(400).json({ error: "nama (string) dan harga (number) wajib" });
  }
  const baru = { id: produk.length + 1, nama, harga };
  produk.push(baru);
  res.status(201).json(baru);
});

app.listen(3000, () => console.log("Server jalan di http://localhost:3000"));

Perhatikan, express.json() ngurus parsing body otomatis—nggak perlu lagi nampung chunk satu-satu. res.json() juga otomatis set header Content-Type: application/json. Kode jadi lebih pendek dan fokus ke logika bisnis, bukan ke plumbing HTTP.

Tapi ingat aturan mainnya: jangan nambah dependency cuma karena keren. Express itu pas kalau aplikasimu memang tumbuh. Buat skrip kecil sekali pakai atau prototipe, http bawaan sering lebih dari cukup. Pilih yang sesuai ukuran masalahmu, bukan yang lagi ngetren.

→ skipped: middleware kompleks, add when: butuh autentikasi dan logging di banyak rute.

Hubungkan ke database sungguhan

Sampai sekarang data kita cuma numpang di array dalam memori. Konsekuensinya jelas: begitu server di-restart, semua data hilang. Buat belajar nggak masalah, tapi buat produksi jelas nggak bisa. Solusinya, sambungkan ke database.

Ada dua jalur populer: database relasional macam PostgreSQL atau MySQL, dan database dokumen macam MongoDB. Gampangnya:

  • PostgreSQL/MySQL cocok kalau datamu punya struktur jelas dan saling berelasi—misalnya produk punya kategori, pesanan punya banyak item, pelanggan punya banyak pesanan. Relasi antar tabel terjaga rapi dan ada jaminan konsistensi.

  • MongoDB cocok kalau struktur datamu fleksibel atau sering berubah, dan kamu nggak mau ribet bikin skema kaku dari awal.

Buat gambaran, beginilah endpoint GET /produk kalau datanya ditarik dari PostgreSQL pakai library pg:

JAVASCRIPT
const { Pool } = require("pg");
const pool = new Pool(); // baca konfigurasi dari env: PGHOST, PGUSER, dst.

app.get("/produk", async (req, res) => {
  try {
    const hasil = await pool.query("SELECT id, nama, harga FROM produk ORDER BY id");
    res.json(hasil.rows);
  } catch (err) {
    // Jangan bocorkan detail error database ke klien
    console.error(err);
    res.status(500).json({ error: "Gagal mengambil data" });
  }
});

Ada dua hal penting yang sengaja nggak di-skip di sini. Pertama, query pakai parameter (nanti kita lihat di contoh berikutnya), bukan string yang disambung manual—ini buat cegah SQL injection. Kedua, error database ditangkap dan dibungkus pesan generik. Jangan pernah lempar pesan error mentah dari database ke klien, karena bisa bocorin struktur internal sistemmu ke orang yang nggak berhak.

Contoh POST dengan parameter yang aman:

JAVASCRIPT
app.post("/produk", async (req, res) => {
  const { nama, harga } = req.body;
  if (!nama || typeof harga !== "number") {
    return res.status(400).json({ error: "nama (string) dan harga (number) wajib" });
  }
  try {
    const hasil = await pool.query(
      "INSERT INTO produk (nama, harga) VALUES ($1, $2) RETURNING *",
      [nama, harga] // nilai dikirim terpisah, bukan disambung ke string SQL
    );
    res.status(201).json(hasil.rows[0]);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Gagal menyimpan data" });
  }
});

Lihat $1 dan $2 itu? Itu placeholder. Nilai aslinya dikirim lewat array terpisah, dan driver database yang ngurus penyisipannya dengan aman. Inilah cara cegah SQL injection—jangan pernah, sekali lagi jangan pernah, nyambung input pengguna langsung ke string SQL macam "... VALUES ('" + nama + "')". Itu lubang keamanan paling klasik dan paling fatal.

→ skipped: connection pooling lanjutan, migrasi skema, add when: trafik naik atau skema sering berubah.

Autentikasi: jangan biarkan API-mu telanjang

Bagian ini ditulis dengan hati-hati, karena salah paham soal keamanan akibatnya nyata dan susah dibalik. Bacalah pelan-pelan.

API yang terbuka tanpa autentikasi sama saja dengan rumah tanpa pintu. Siapa pun bisa masuk, ambil data, bahkan mengubahnya. Ada tiga pendekatan umum, dari yang paling sederhana ke yang paling kuat.

1. API Key

Cara paling sederhana. Setiap klien dapat sebuah kunci unik, dan kunci itu dikirim di setiap request lewat header. Server mengecek apakah kunci itu valid sebelum memproses permintaan.

JAVASCRIPT
// Middleware cek API key — pasang sebelum rute yang dilindungi
function cekApiKey(req, res, next) {
  const key = req.headers["x-api-key"];
  if (!key || key !== process.env.API_KEY) {
    return res.status(401).json({ error: "API key tidak valid" });
  }
  next();
}

app.use("/produk", cekApiKey); // semua rute /produk butuh key

Penting: simpan kunci asli di environment variable (process.env.API_KEY), jangan pernah ditulis langsung di kode. Kalau kunci ke-hardcode di kode lalu kode itu masuk ke repository publik, kuncimu bocor ke seluruh dunia. Ini kesalahan yang sering terjadi dan akibatnya bisa fatal.

API key cocok untuk komunikasi antar-server atau saat kamu cuma butuh tahu "request ini dari klien mana". Tapi dia punya kelemahan: kalau bocor, siapa pun yang pegang kuncinya bisa menyamar jadi klien itu. Makanya kunci harus dijaga ketat dan diputar (di-rotate) secara berkala.

2. JWT (JSON Web Token)

JWT lebih cocok untuk autentikasi pengguna. Alurnya begini, dan urutannya penting:

  1. Pengguna login dengan email dan password.

  2. Server memverifikasi kredensial. Kalau benar, server menerbitkan sebuah token yang sudah ditandatangani secara kriptografis.

  3. Token itu dikirim balik ke klien, lalu klien menyimpannya.

  4. Untuk setiap permintaan berikutnya, klien melampirkan token di header Authorization: Bearer <token>.

  5. Server memverifikasi tanda tangan token. Kalau valid dan belum kedaluwarsa, permintaan diproses.

Yang elegan dari JWT adalah sifatnya yang sejalan dengan prinsip stateless REST. Server nggak perlu menyimpan sesi di memori atau database. Semua informasi yang dibutuhkan—siapa penggunanya, kapan token kedaluwarsa—sudah terbungkus di dalam token itu sendiri, dan keasliannya dijamin oleh tanda tangan.

JAVASCRIPT
const jwt = require("jsonwebtoken");

// Saat login berhasil, terbitkan token
function buatToken(user) {
  return jwt.sign(
    { id: user.id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn: "1h" } // token kedaluwarsa dalam 1 jam
  );
}

// Middleware verifikasi token
function cekToken(req, res, next) {
  const header = req.headers["authorization"];
  const token = header && header.split(" ")[1]; // ambil bagian setelah "Bearer"
  if (!token) {
    return res.status(401).json({ error: "Token tidak ada" });
  }
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    return res.status(401).json({ error: "Token tidak valid atau kedaluwarsa" });
  }
}

Beberapa aturan keamanan yang nggak boleh ditawar:

  • JWT_SECRET harus rahasia dan panjang. Ini kunci yang dipakai menandatangani token. Kalau bocor, penyerang bisa bikin token palsu seenaknya. Simpan di environment variable, jangan di kode.

  • Selalu set masa kedaluwarsa (expiresIn). Token yang berlaku selamanya itu berbahaya—kalau dicuri, bisa dipakai tanpa batas waktu.

  • Jangan simpan data sensitif di dalam token. Isi JWT bisa dibaca siapa saja yang memegangnya (cuma di-encode, bukan di-enkripsi). Jadi jangan pernah taruh password atau data pribadi di sana.

3. OAuth 2.0

OAuth 2.0 adalah standar paling lengkap, dan ini yang kamu pakai tiap kali login ke suatu aplikasi pakai akun Google atau Facebook. Bedanya dengan dua cara sebelumnya: OAuth memungkinkan pengguna memberi izin terbatas ke aplikasi pihak ketiga tanpa harus membagikan password mereka.

Misalnya, sebuah aplikasi mau mengakses kalender Google kamu. Dengan OAuth, kamu mengizinkan akses ke kalender saja—bukan ke seluruh akun Google—dan tanpa pernah memberi tahu password Google kamu ke aplikasi itu. Aplikasi cuma dapat token akses dengan cakupan terbatas.

OAuth lebih kompleks untuk dibangun sendiri, jadi untuk kebanyakan kasus kamu bakal pakai layanan yang sudah ada (seperti Auth0, atau provider identitas dari Google/Microsoft). Jangan bikin sendiri dari nol kecuali kamu benar-benar tahu apa yang kamu lakukan—keamanan adalah area di mana "bikin sendiri" sering berakhir buruk.

Resume gaya santai. Intinya: pilih API key buat hal sederhana antar-server, JWT buat autentikasi pengguna di aplikasimu sendiri, dan OAuth 2.0 kalau kamu butuh integrasi dengan akun pihak ketiga. Dan apa pun pilihanmu, selalu jalankan API di atas HTTPS. Tanpa enkripsi, token dan password kamu melayang di jaringan dalam bentuk teks polos yang bisa disadap siapa saja.

Rate limiting dan kuota: jaga API tetap sehat

Bayangin API kamu tiba-tiba dihujani jutaan request dalam semenit—entah karena ada bug di klien, atau ada yang sengaja nyerang. Tanpa pembatas, server bisa tumbang. Di sinilah rate limiting berperan: membatasi berapa banyak request yang boleh dilakukan satu klien dalam rentang waktu tertentu.

Konsep ini bukan teori belaka—hampir semua API komersial menerapkannya. Coba lihat pola harga yang lazim di provider API lokal seperti Api.co.id: paket Standard membatasi 20 request per detik, sementara paket Premium dinaikkan ke 100 request per detik.[^{{https://api.co.id}}] Selain rate limit per detik, mereka juga menerapkan kuota bulanan: paket gratis dapat 3.000 hits berhasil per bulan, dan menariknya, poin cuma dipotong untuk request yang berhasil.[^{{https://api.co.id}}] Detail kecil itu penting buat pengguna—kamu nggak kena charge gara-gara request yang gagal.

Implementasi sederhananya bisa pakai library express-rate-limit:

JAVASCRIPT
const rateLimit = require("express-rate-limit");

const limiter = rateLimit({
  windowMs: 60 * 1000, // jendela 1 menit
  max: 100,            // maksimal 100 request per menit per IP
  message: { error: "Terlalu banyak request, coba lagi nanti" },
});

app.use(limiter);

Saat klien melewati batas, server membalas dengan status 429 Too Many Requests. Ini status code yang spesifik buat kasus ini, dan klien yang baik harusnya menghormatinya—biasanya dengan menunggu sebentar sebelum mencoba lagi. Beberapa API bahkan mengirim header Retry-After yang memberi tahu klien berapa detik harus menunggu.

→ skipped: rate limit terdistribusi pakai Redis, add when: API jalan di banyak server sekaligus (IP counter di memori nggak nyambung antar-server).

Pagination, filtering, dan sorting: jangan kirim semua sekaligus

Misalkan tabel produkmu sudah berisi 50.000 baris. Apa yang terjadi kalau klien memanggil GET /produk? Server bakal mencoba mengirim semuanya dalam satu respons raksasa. Lambat, boros bandwidth, dan bikin aplikasi klien lemot. Solusinya tiga: pagination, filtering, dan sorting.

Pagination memecah data jadi halaman-halaman kecil. Klien minta halaman ke berapa dan berapa item per halaman, lewat query parameter:

JAVASCRIPT
app.get("/produk", async (req, res) => {
  // Default aman + batas atas biar klien nggak bisa minta sejuta sekaligus
  const limit = Math.min(Number(req.query.limit) || 10, 100);
  const page = Math.max(Number(req.query.page) || 1, 1);
  const offset = (page - 1) * limit;

  const hasil = await pool.query(
    "SELECT id, nama, harga FROM produk ORDER BY id LIMIT $1 OFFSET $2",
    [limit, offset]
  );
  res.json({ page, limit, data: hasil.rows });
});

Perhatikan Math.min(..., 100). Ini bukan basa-basi—ini perlindungan. Tanpa batas atas, klien bisa mengirim ?limit=999999 dan memaksa server menarik seluruh tabel, persis masalah yang mau kita hindari. Selalu kasih nilai default yang masuk akal dan batas maksimal.

Pemanggilannya jadi seperti ini:

BASH
curl "http://localhost:3000/produk?page=2&limit=20"

Filtering memungkinkan klien menyaring data sesuai kriteria, misalnya cuma produk di bawah harga tertentu: GET /produk?harga_maks=200000. Sorting mengatur urutan, misalnya GET /produk?sort=harga&order=desc. Keduanya juga diteruskan lewat query parameter, dan—ini penting—nilai-nilainya tetap harus divalidasi dan dimasukkan ke query lewat parameter berparameter, bukan disambung manual. Sekali lagi, supaya aman dari injection.

Pola yang dipakai provider API besar pun mirip. Saat sebuah layanan menyediakan, misalnya, data wilayah Indonesia dari 38 provinsi sampai ke tingkat kelurahan,[^{{https://api.co.id}}] mustahil semuanya dikirim sekaligus. Pasti ada mekanisme untuk minta per provinsi, per kota, atau per halaman. Itulah pagination dan filtering dalam praktik nyata.

CORS: kenapa request dari browser sering ditolak

Ini salah satu error yang paling sering bikin developer frontend garuk-garuk kepala. Kamu sudah bikin API yang jalan sempurna saat dites pakai cURL atau Postman, tapi begitu dipanggil dari kode JavaScript di browser, muncul pesan merah:

CODE
Access to fetch at 'http://localhost:3000/produk' from origin 'http://localhost:5173'
has been blocked by CORS policy

Tenang, ini bukan bug di API kamu. Ini fitur keamanan browser bernama CORS (Cross-Origin Resource Sharing). Singkatnya, browser secara default melarang halaman web di satu domain (misalnya localhost:5173) mengambil data dari domain lain (misalnya localhost:3000), kecuali server tujuan secara eksplisit mengizinkannya.

Kenapa aturan ini ada? Demi melindungi pengguna. Tanpa CORS, situs jahat bisa diam-diam mengirim request ke API bank kamu menggunakan sesi login kamu yang masih aktif. CORS memaksa server menyatakan secara eksplisit siapa saja yang boleh mengaksesnya.

Cara membenahinya—dari sisi server, bukan klien—adalah dengan mengirim header Access-Control-Allow-Origin. Di Express, paling gampang pakai library cors:

JAVASCRIPT
const cors = require("cors");

// Versi longgar (cuma buat development) — izinkan semua origin
app.use(cors());

// Versi produksi — izinkan origin tertentu saja
app.use(cors({ origin: "https://aplikasimu.com" }));

Hati-hati di sini. app.use(cors()) tanpa argumen mengizinkan semua origin, dan itu praktis cuma boleh dipakai saat development. Di produksi, kamu wajib membatasi ke domain frontend kamu yang sah saja. Membuka CORS ke semua origin di produksi sama dengan melonggarkan satu lapis perlindungan yang sengaja dipasang browser. Sesuaikan dengan kebutuhan, jangan asal buka lebar.

Studi kasus: membedah cara kerja provider API lokal

Teori tadi bakal lebih nyantol kalau kita lihat penerapannya di layanan nyata. Mari kita bedah sebuah provider API lokal sebagai contoh—Api.co.id, yang memposisikan diri sebagai penyedia API terlengkap untuk pasar Indonesia.[^{{https://api.co.id}}]

Yang menarik dari model seperti ini adalah bagaimana semua prinsip REST yang kita bahas tadi muncul jadi keputusan bisnis dan teknis yang konkret.

Katalog produk sebagai kumpulan resource. Layanan ini menyediakan beragam API dalam satu platform: OCR KTP untuk verifikasi identitas, data wilayah Indonesia, jadwal sholat dan imsakiyah, IP Geolocation, hingga Exchange Rate untuk kurs mata uang.[^{{https://api.co.id}}] Tiap produk pada dasarnya adalah satu kumpulan resource dengan endpoint-nya sendiri. Kamu tinggal pilih yang dibutuhkan, panggil endpoint-nya, dapat JSON balik. Inilah keindahan uniform interface—pola pemanggilannya seragam meski isinya beda-beda.

Model harga yang mencerminkan sifat stateless dan per-request. Karena tiap request REST berdiri sendiri (ingat, stateless), wajar kalau penagihannya juga per-request. Modelnya pay-as-you-go, mulai dari sekitar Rp 300 per request untuk layanan berbayar seperti OCR KTP, sementara beberapa API seperti data wilayah dan jadwal sholat ditawarkan gratis.[^{{https://api.co.id}}] Tiap panggilan dihitung independen, persis seperti karakter request REST yang nggak saling bergantung.

Versioning di endpoint. Coba perhatikan pola URL yang dipakai banyak provider: https://api.co.id/v1/produk/123. Ada /v1/ di situ—penanda versi. Ini bukan hiasan. Kalau suatu saat mereka mengubah struktur respons secara besar-besaran, mereka bisa rilis /v2/ tanpa merusak aplikasi pelanggan lama yang masih nempel di /v1/. Persis praktik terbaik yang kita bahas sebelumnya.

Autentikasi berbasis API key. Untuk mulai pakai, kamu daftar dan dapat API key gratis. Key itu yang kamu kirim di tiap request supaya server tahu request itu dari kamu, dan menghitung kuotanya ke akunmu. Paket gratisnya memberi 3.000 hits berhasil per bulan dengan rate limit 20 request/detik.[^{{https://api.co.id}}] Ini contoh nyata API key plus rate limiting plus kuota bekerja bersamaan—tiga konsep yang baru kita bahas, menyatu dalam satu produk.

Caching untuk data yang jarang berubah. Data wilayah Indonesia—daftar provinsi, kota, kecamatan—nyaris nggak berubah, paling diperbarui sesekali mengikuti Permendagri.[^{{https://api.co.id}}] Data seperti ini kandidat sempurna untuk di-cache. Aplikasi yang pakai data ini buat autofill form alamat nggak perlu memanggil server tiap kali; cukup simpan hasilnya dan pakai ulang. Inilah prinsip cacheable yang menghemat biaya sekaligus mempercepat aplikasi.

Pelajaran dari studi kasus ini: prinsip-prinsip REST yang kelihatannya abstrak di atas kertas sebenarnya jadi tulang punggung keputusan nyata—mulai dari struktur URL, model harga, sampai strategi caching. Begitu kamu paham prinsipnya, kamu bisa "membaca" desain API mana pun dan menebak alasan di balik tiap keputusannya.

Mengonsumsi API publik dari kode kamu

Sejauh ini kita banyak bikin API. Sekarang giliran jadi pengguna—memanggil API orang lain dari kode kita. Skenario ini malah lebih sering kamu temui sehari-hari: aplikasimu butuh data kurs, data alamat, atau hasil verifikasi, dan kamu ambil dari provider yang sudah jadi.

Misalnya kamu mau ambil data kurs mata uang dari sebuah Exchange Rate API yang mendukung 170+ mata uang.[^{{https://api.co.id}}] Di Node.js modern, kamu bisa pakai fetch yang sekarang sudah bawaan—nggak perlu install library tambahan macam axios cuma buat ini:

JAVASCRIPT
async function ambilKurs(dari, ke) {
  const url = `https://api.co.id/v1/exchange?from=${dari}&to=${ke}`;
  const res = await fetch(url, {
    headers: { "x-api-key": process.env.API_KEY },
  });

  // Selalu cek status sebelum percaya datanya
  if (!res.ok) {
    throw new Error(`API balik status ${res.status}`);
  }
  return res.json();
}

// Pakai
ambilKurs("USD", "IDR")
  .then((data) => console.log(data))
  .catch((err) => console.error("Gagal ambil kurs:", err.message));

Ada satu kebiasaan yang wajib kamu bawa tiap kali mengonsumsi API: selalu cek res.ok (atau status code) sebelum memproses datanya. Banyak developer langsung memanggil res.json() dan menganggap semuanya pasti sukses. Padahal API bisa membalas 401 karena key salah, 429 karena kena rate limit, atau 500 karena server mereka lagi bermasalah. Kalau kamu nggak mengeceknya, kode kamu bakal error di tempat yang membingungkan, jauh dari sumber masalah sebenarnya.

Pakai fetch bawaan, bukan nambah axios, adalah contoh prinsip hemat dependency. Buat kebutuhan request sederhana, fetch sudah lengkap. Tambah library cuma kalau kamu butuh fitur yang memang nggak ada—misalnya interceptor otomatis di banyak tempat. Jangan menambah berat proyek untuk sesuatu yang sudah disediakan platform.

→ skipped: retry otomatis dan timeout, add when: API tujuan sering lambat atau labil.

Dokumentasi API: tanpa ini, API-mu setengah jadi

API yang hebat tapi nggak ada dokumentasinya itu seperti perpustakaan tanpa katalog—isinya berharga, tapi nggak ada yang bisa menemukan apa yang dibutuhkan. Dokumentasi bukan pelengkap; dia bagian dari produk.

Dokumentasi API yang baik minimal menjawab pertanyaan-pertanyaan ini untuk tiap endpoint:

  • Apa URL dan HTTP method-nya?

  • Parameter apa yang diterima, mana yang wajib mana yang opsional?

  • Header apa yang dibutuhkan (terutama soal autentikasi)?

  • Seperti apa contoh request dan contoh respons-nya?

  • Status code apa saja yang mungkin dikembalikan, dan apa artinya?

Standar industri untuk ini namanya OpenAPI (dulu dikenal sebagai Swagger). Intinya, kamu menulis spesifikasi API dalam format terstruktur (YAML atau JSON), lalu dari situ bisa dihasilkan halaman dokumentasi interaktif secara otomatis. Pengguna bahkan bisa mencoba endpoint langsung dari halaman dokumentasi, tanpa nulis kode dulu.

Kenapa ini penting buat kredibilitas? Karena dokumentasi yang lengkap menandakan API itu dirawat serius. Bukan kebetulan kalau provider API yang kredibel selalu menonjolkan dokumentasi sebagai nilai jual—dokumentasi lengkap dalam Bahasa Indonesia dan Inggris, plus contoh kode untuk berbagai bahasa pemrograman, jelas mempercepat integrasi dan mengurangi pertanyaan ke tim support.[^{{https://api.co.id}}] Buat developer yang mau pakai, dokumentasi yang jelas itu sering jadi faktor penentu: antara langsung jalan dalam sejam, atau frustrasi seharian menebak-nebak.

Saranku: tulis dokumentasi sambil kamu bikin API-nya, bukan ditunda sampai akhir. Dokumentasi yang ditunda biasanya nggak pernah kelar, atau kelar tapi sudah nggak akurat.

Versioning yang benar: jangan bikin pelanggan marah

Kita sudah sebut versioning beberapa kali, tapi ini layak dapat perhatian khusus karena salah langkah di sini bisa merusak kepercayaan pengguna. Bayangkan ada ratusan aplikasi yang bergantung pada API kamu. Suatu hari kamu ubah nama field dari nama_produk jadi product_name. Niatnya rapi, tapi akibatnya: semua aplikasi itu langsung rusak serentak. Mimpi buruk.

Aturannya sederhana tapi tegas: begitu sebuah API sudah dipakai orang lain, jangan pernah mengubahnya dengan cara yang merusak (breaking change) di versi yang sama. Kalau kamu butuh perubahan besar, rilis versi baru.

Cara paling umum adalah memasukkan versi ke dalam URL:

CODE
https://api.co.id/v1/produk
https://api.co.id/v2/produk

Dengan begini, aplikasi lama tetap aman di /v1/, sementara aplikasi baru bisa pindah ke /v2/ saat mereka siap. Kamu kasih waktu transisi, umumkan jadwal penghentian versi lama jauh-jauh hari, baru matikan /v1/ setelah semua pengguna sempat bermigrasi.

Apa saja yang termasuk breaking change? Menghapus field, mengubah nama field, mengubah tipe data (dari angka jadi string), atau mengubah aturan validasi jadi lebih ketat. Sebaliknya, menambah field baru yang opsional biasanya aman—klien lama tinggal mengabaikan field yang nggak mereka kenal. Jadi kalau bisa, tambah; hindari mengubah atau menghapus.

Tingkat kematangan REST: Richardson Maturity Model

Buat kamu yang mau paham lebih dalam apa artinya "RESTful sungguhan", ada kerangka berpikir yang berguna namanya Richardson Maturity Model. Model ini membagi kematangan sebuah API jadi empat level, dari Level 0 sampai Level 3.

  • Level 0 — Satu pintu untuk semua. Semua request dikirim ke satu URL dengan satu method (biasanya POST). Ini belum benar-benar REST, lebih mirip gaya RPC lama. Tindakan dibedakan lewat isi body, bukan lewat URL atau method.

  • Level 1 — Mengenal resource. Mulai ada banyak URL untuk resource berbeda: /produk, /pelanggan, /pesanan. Tapi method-nya masih seragam, belum dimanfaatkan dengan benar.

  • Level 2 — Memakai HTTP method dengan benar. Di sinilah mayoritas API "RESTful" di dunia nyata berada. GET buat baca, POST buat bikin, PUT buat update, DELETE buat hapus, dan status code dipakai dengan tepat. API yang kita bangun di tutorial ini ada di level ini.

  • Level 3 — HATEOAS. Ini puncaknya, dan paling jarang diterapkan sepenuhnya. Singkatan dari Hypermedia as the Engine of Application State. Idenya, tiap respons API nggak cuma membawa data, tapi juga menyertakan tautan ke aksi-aksi terkait yang bisa dilakukan selanjutnya.

Contoh respons Level 3 kira-kira begini:

JSON
{
  "id": 123,
  "nama": "Sepatu Lari",
  "harga": 500000,
  "_links": {
    "self": { "href": "/v1/produk/123" },
    "update": { "href": "/v1/produk/123", "method": "PUT" },
    "hapus": { "href": "/v1/produk/123", "method": "DELETE" }
  }
}

Dengan HATEOAS, klien nggak perlu hafal struktur URL—dia tinggal mengikuti tautan yang disediakan server, mirip cara kamu menjelajah web dengan mengeklik link, bukan mengetik alamat manual. Idealnya bagus, tapi kenyataannya rumit untuk diterapkan dan jarang benar-benar dibutuhkan. Buat kebanyakan kasus, Level 2 sudah lebih dari cukup, dan itu sah-sah saja. Jangan memaksakan HATEOAS cuma demi terlihat "REST murni" kalau aplikasimu nggak butuh.

Menguji API: satu langkah kecil yang menyelamatkan banyak waktu

Pengujian sering dianggap beban, padahal dia justru menghemat waktu dalam jangka panjang. Bug yang ketahuan saat development jauh lebih murah daripada bug yang ketahuan setelah pengguna komplain.

Ada beberapa lapis pengujian, dari yang paling ringan:

Manual pakai cURL atau Postman. Cara tercepat saat mengembangkan. Kamu kirim request, lihat hasilnya. Postman bahkan punya antarmuka yang enak buat menyimpan koleksi request, mengatur environment, dan menjalankannya berulang. Cocok buat eksplorasi cepat.

Pengujian otomatis. Untuk logika yang nggak sepele, sediakan satu pengecekan yang bisa dijalankan ulang kapan saja. Nggak perlu langsung pasang framework testing yang berat. Sebuah skrip kecil berbasis assert sudah memberi nilai besar:

JAVASCRIPT
const assert = require("assert");

async function uji() {
  // Tes 1: GET balik status 200 dan berupa array
  let res = await fetch("http://localhost:3000/produk");
  assert.strictEqual(res.status, 200);
  assert.ok(Array.isArray(await res.json()));

  // Tes 2: POST dengan data tidak valid harus ditolak 400
  res = await fetch("http://localhost:3000/produk", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ nama: "Tanpa Harga" }), // sengaja salah
  });
  assert.strictEqual(res.status, 400);

  console.log("OK: semua pengecekan dasar lolos");
}

uji().catch((err) => {
  console.error("GAGAL:", err.message);
  process.exit(1);
});

Yang penting di sini bukan cuma menguji jalur yang benar (happy path), tapi juga jalur yang salah. Tes kedua sengaja mengirim data tanpa harga, dan kita pastikan server menolaknya dengan 400. Pengujian terhadap input buruk justru sering lebih berharga, karena di situlah bug suka bersembunyi. Kalau validasimu diam-diam jebol, tes inilah yang bakal menangkapnya sebelum pengguna yang menemukannya.

→ skipped: framework testing penuh (Jest/Vitest) dan mock database, add when: jumlah endpoint dan kasus uji sudah banyak.

Idempotensi: kenapa PUT dan DELETE aman diulang

Ada satu konsep yang sering kelewat padahal penting buat keandalan: idempotensi. Sebuah operasi disebut idempoten kalau menjalankannya sekali atau berkali-kali memberi hasil akhir yang sama.

Kenapa ini penting? Bayangin jaringan klien putus-nyambung. Klien mengirim request, tapi nggak yakin apakah sampai ke server atau nggak. Kalau dia kirim ulang, apa yang terjadi?

  • GET idempoten. Membaca data sepuluh kali nggak mengubah apa pun. Aman diulang.

  • PUT idempoten. Mengirim "set harga produk 1 jadi 750000" dua kali hasilnya tetap sama—harganya 750000. Aman diulang.

  • DELETE idempoten. Menghapus produk 1 dua kali hasilnya sama: produk itu nggak ada. (Permintaan kedua mungkin balas 404, tapi keadaan akhirnya tetap sama.)

  • POST tidak idempoten. Mengirim "buat produk baru" dua kali bikin dua produk. Inilah kenapa kamu kadang nggak sengaja kepencet tombol bayar dua kali dan muncul dua transaksi.

Pemahaman ini punya konsekuensi praktis. Untuk operasi yang nggak idempoten seperti POST pembayaran, sistem yang baik menambahkan pengaman seperti idempotency key—sebuah kunci unik yang dikirim klien, sehingga kalau request yang sama terkirim dua kali, server tahu itu duplikat dan cuma memprosesnya sekali. Inilah cara payment gateway mencegah transaksi ganda. Jadi saat kamu mendesain API, tanyakan: "kalau request ini terkirim dua kali, apakah aman?" Jawabannya menentukan seberapa banyak pengaman yang perlu kamu pasang.

Logging dan monitoring: jadi mata dan telinga saat produksi

Begitu API kamu hidup di produksi dan dipakai orang, kamu butuh cara untuk tahu apa yang sedang terjadi. Tanpa logging, saat ada yang error kamu cuma bisa nebak-nebak. Dengan logging, kamu punya jejak untuk ditelusuri.

Minimal, catat tiap request yang masuk dan tiap error yang terjadi:

JAVASCRIPT
// Middleware logging sederhana — pasang paling awal
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
  next();
});

Buat awal, console.log sudah cukup membantu. Tapi seiring API tumbuh, kamu bakal mau hal-hal seperti: berapa lama tiap request diproses, endpoint mana yang paling sering dipanggil, dan berapa persen request yang berakhir error. Metrik-metrik ini yang bikin kamu bisa menjawab pertanyaan penting—misalnya "kenapa aplikasi terasa lambat sejak kemarin sore?"—dengan data, bukan tebakan.

Satu hal yang nggak boleh masuk ke log: data sensitif. Jangan pernah mencatat password, token penuh, atau data pribadi pengguna ke dalam log. Log sering disimpan dalam teks polos dan diakses banyak orang di tim, jadi mencatat rahasia di sana sama saja membocorkannya secara halus. Kalau perlu mencatat token buat debugging, potong sebagian saja (misalnya cuma beberapa karakter terakhir).

Checklist sebelum API naik ke produksi

Sebelum kamu melepas API ke dunia nyata, lewati daftar ini dulu. Anggap ini rem terakhir sebelum tancap gas:

  • Semua endpoint pakai HTTPS, bukan HTTP polos.

  • Ada autentikasi di endpoint yang sensitif (API key, JWT, atau OAuth sesuai kebutuhan).

  • Validasi input terpasang di tiap titik masuk data, dan menolak data yang nggak sesuai dengan status 400.

  • Query ke database pakai parameter, bukan string yang disambung manual (cegah SQL injection).

  • Rate limiting aktif biar API nggak gampang dibanjiri request.

  • CORS dikonfigurasi ketat ke domain yang sah, bukan dibuka ke semua origin.

  • Endpoint yang balikin banyak data sudah pakai pagination dengan batas atas yang aman.

  • Status code dipakai dengan benar dan konsisten di seluruh endpoint.

  • Pesan error informatif buat developer, tapi nggak membocorkan detail internal sistem.

  • Ada versi di URL biar perubahan ke depan nggak merusak klien lama.

  • Logging aktif, dan dipastikan nggak mencatat data sensitif.

  • Ada minimal satu pengecekan otomatis yang menguji jalur sukses dan jalur gagal.

  • Dokumentasi tersedia dan sesuai dengan perilaku API yang sebenarnya.

Checklist ini bukan formalitas. Tiap poinnya mewakili satu kelas masalah nyata yang sering bikin API bermasalah di produksi—mulai dari kebocoran data, transaksi ganda, sampai aplikasi klien yang tiba-tiba rusak. Lewati satu, dan kamu sedang menumpuk utang teknis yang cepat atau lambat bakal ditagih, biasanya di waktu yang paling nggak nyaman.


Referensi

Google Cloud. (2026). Dasar-Dasar dan Implementasi REST API.

Informatika Alma Ata. (2026). Mengenal REST API dan Cara Kerjanya.

Api.co.id. (2026). Apa Itu REST API? Pengertian, Prinsip, dan Cara Kerjanya.

Digital Skola. (2026). Apa Itu REST API? Prinsip, Cara Kerja, dan Contohnya.

Dibimbing. (2026). Apa Itu REST API? Fungsi, Cara Kerja, dan Contoh.

Anak Informatika. (2026). API dan REST API: Penjelasan Lengkap, Contoh, dan Cara Kerjanya.

Rumahweb. (2026). REST API: Pengertian dan Perbedaannya dengan RESTful API.

Exabytes. (2026). Apa Itu REST API? Fungsi, Cara Kerja, dan Contohnya.

Komentar (0)

Belum ada komentar. Jadilah yang pertama berbagi pendapat!

Tinggalkan komentar