Mengambil data dari API bukan lagi keahlian khusus — hampir semua aplikasi modern, dari bot sederhana sampai sistem enterprise besar, memanggil REST API untuk data cuaca, pembayaran, atau pengiriman. Meskipun library-nya sudah kita kuasai, mengkonsumsi API tanpa memahami prinsip REST membuat kita terjebak menebak-nebak: kenapa method PUT bukan POST, kenapa ada /api/produk/1, kenapa tiap permintaan harus membawa token. Chapter ini menjawab ketiganya sambil membangun API latihan lokal yang bisa kita panggil sungguhan.

Prinsip REST #

REST bukan protokol melainkan sekumpulan konvensi arsitektur di atas HTTP. Tiga prinsipnya yang paling terasa saat menulis klien:

  • Resource sebagai kata benda — URL menunjuk “benda”, bukan aksi. /api/produk/1 adalah sebuah produk; aksi (ambil, ubah, hapus) diambil alih oleh method HTTP.
  • Method HTTP sebagai kata kerja — GET membaca, POST membuat, PUT mengganti, DELETE menghapus. Satu URL /api/produk/1 bisa berarti empat hal berbeda bergantung methodnya.
  • Stateless — setiap permintaan berdiri sendiri dan membawa semua informasi yang dibutuhkan server (biasanya di header Authorization). Server tidak mengingat permintaan sebelumnya, sehingga permintaan kedua dari klien yang sama diperlakukan seperti klien baru.

Konsekuensi praktis prinsip stateless: tidak ada session login seperti di aplikasi web tradisional. Setiap permintaan membawa bukti identitasnya sendiri.

Operasi CRUD #

Istilah CRUD (Create, Read, Update, Delete) adalah kosakata dasar pengelolaan data. Pemetaannya ke HTTP sangat rapi:

Operasi CRUDMethod HTTPURL contohStatus sukses
CreatePOST/api/produk201 Created
ReadGET/api/produk atau /api/produk/1200 OK
UpdatePUT (utuh) / PATCH (sebagian)/api/produk/1200 OK
DeleteDELETE/api/produk/1204 No Content

API Lokal untuk Latihan #

Kita bangun API latihan dengan Flask — terpasang lokal dan bisa dipanggil tanpa membuka port, karena kita memakai test_client() milik Flask. Simpan sebagai latihan_rest.py:

python
from flask import Flask, jsonify, request

app = Flask(__name__)
app.json.sort_keys = False
produk = {
    1: {"nama": "Kopi Gayo", "harga": 85000},
    2: {"nama": "Teh Melati", "harga": 45000},
}


@app.get("/api/produk")
def daftar_produk():
    return jsonify({"data": [{"id": k, **v} for k, v in produk.items()]})


@app.post("/api/produk")
def tambah_produk():
    payload = request.get_json()
    id_baru = max(produk) + 1
    produk[id_baru] = {"nama": payload["nama"], "harga": payload["harga"]}
    return jsonify({"id": id_baru, **produk[id_baru]}), 201


@app.put("/api/produk/<int:id_produk>")
def ubah_produk(id_produk):
    if id_produk not in produk:
        return jsonify({"error": "produk tidak ditemukan"}), 404
    payload = request.get_json()
    produk[id_produk] = {"nama": payload["nama"], "harga": payload["harga"]}
    return jsonify({"id": id_produk, **produk[id_produk]})


@app.delete("/api/produk/<int:id_produk>")
def hapus_produk(id_produk):
    if id_produk not in produk:
        return jsonify({"error": "produk tidak ditemukan"}), 404
    del produk[id_produk]
    return "", 204


@app.get("/api/laporan")
def laporan():
    token = request.headers.get("Authorization", "")
    if token != "Bearer YOUR_API_TOKEN":
        return jsonify({"error": "token tidak valid"}), 401
    return jsonify({"total_produk": len(produk)})


klien = app.test_client()

respons = klien.get("/api/produk")
print("GET   :", respons.status_code, respons.get_json())

respons = klien.post("/api/produk", json={"nama": "Gula Aren", "harga": 32000})
print("POST  :", respons.status_code, respons.get_json())

respons = klien.put("/api/produk/1", json={"nama": "Kopi Toraja", "harga": 90000})
print("PUT   :", respons.status_code, respons.get_json())

respons = klien.delete("/api/produk/2")
print("DELETE:", respons.status_code)

respons = klien.get("/api/laporan")
print("Laporan tanpa token  :", respons.status_code, respons.get_json())

respons = klien.get("/api/laporan", headers={"Authorization": "Bearer YOUR_API_TOKEN"})
print("Laporan dengan token :", respons.status_code, respons.get_json())

Output yang Dihasilkan:

text
GET   : 200 {'data': [{'id': 1, 'nama': 'Kopi Gayo', 'harga': 85000}, {'id': 2, 'nama': 'Teh Melati', 'harga': 45000}]}
POST  : 201 {'id': 3, 'nama': 'Gula Aren', 'harga': 32000}
PUT   : 200 {'id': 1, 'nama': 'Kopi Toraja', 'harga': 90000}
DELETE: 204
Laporan tanpa token  : 401 {'error': 'token tidak valid'}
Laporan dengan token : 200 {'total_produk': 2}

Mari kita telaah bagian-bagian kuncinya:

  1. Baris 11 dan 16 (@app.get / @app.post): Decorator ini (tersedia sejak Flask 2.0) mendaftarkan function sebagai penangani satu method dan satu URL sekaligus.
  2. Baris 24 (<int:id_produk>): Bagian dinamis URL ditangkap sebagai argument id_produk — inilah makna “resource produk nomor 1” pada /api/produk/1.
  3. Baris 27, 36, dan 38 (, 404 / , 204): Tuple (body, status) pada return Flask menyatakan response body beserta status code secara eksplisit.
  4. Baris 49 (klien = app.test_client()): test_client() mengembalikan object klien yang memanggil aplikasi secara langsung di dalam satu proses — cocok untuk latihan dan testing, tanpa menjalankan server sungguhan.

Autentikasi dengan Header Authorization #

Dua panggilan terakhir pada output di atas memperlihatkan pola autentikasi paling umum: header Authorization bernilai "Bearer <token>". Tanpa token yang sah, server menjawab 401 Unauthorized; dengan token yang cocok, laporan diberikan. Pada API nyata, token didapat dari dashboard penyedia layanan dan disimpan di environment variable — jangan pernah ditulis langsung di kode. Placeholder YOUR_API_TOKEN pada contoh kita mengikuti konvensi yang sama.

Melangkah ke Pagination #

API nyata jarang mengirim seluruh data sekaligus — daftar ribuan produk dipecah menjadi halaman. Mekanisme meminta halaman demi halaman, serta menghormati batas jumlah permintaan, adalah dua keterampilan penutup Section ini yang kita bahas pada Chapter Pagination dan Rate Limits.