Idenya
Tidak semua client adalah orang dengan browser. Aplikasi mobile, dashboard Streamlit, sistem milik partner, atau JavaScript buatanmu sendiri semuanya menginginkan data, bukan HTML. API (Application Programming Interface) menjawab request dengan JSON, format yang sudah kamu parse di track Python.
Sebagian besar API web mengikuti konvensi bernama REST: URL menamai resource (benda), dan method HTTP menyatakan apa yang dilakukan terhadapnya.
| Method + URL | Arti | Status sukses |
|---|---|---|
GET /api/products | daftar produk | 200 |
GET /api/products/3 | membaca produk 3 | 200 (404 kalau tidak ada) |
POST /api/products | membuat produk | 201 |
PATCH /api/products/3 | memperbarui sebagian field | 200 |
DELETE /api/products/3 | menghapusnya | 204 (tanpa body) |
Perhatikan bahwa URL-nya berupa kata benda (/products), tidak pernah kata kerja (/getProducts, /deleteProduct): method-lah kata kerjanya.
Mengembalikan JSON
Kembalikan dict atau list, dan Flask mengubahnya menjadi JSON dengan Content-Type yang tepat:
from flask import Flask
app = Flask(__name__)
PRODUCTS = {
1: {"id": 1, "name": "French Press 600ml", "price": 185000},
2: {"id": 2, "name": "Pour-over Kettle", "price": 320000},
}
@app.get("/api/products")
def list_products():
return {"products": list(PRODUCTS.values()), "count": len(PRODUCTS)}
r = app.test_client().get("/api/products")
print(r.status_code, r.mimetype)
print(r.get_json())
200 application/json
{'count': 2, 'products': [{'id': 1, 'name': 'French Press 600ml', 'price': 185000}, {'id': 2, 'name': 'Pour-over Kettle', 'price': 320000}]}
r.get_json() di test client mem-parse body kembali menjadi Python. (jsonify(...) sama saja dengan mengembalikan dict; kamu akan melihatnya di kode yang lebih lama.)
Membungkus list di dalam objek ({"products": [...], "count": 2}) adalah kebiasaan yang baik: kamu bisa menambah field nanti (paging, total) tanpa merusak client.
Membaca JSON dari request
Aplikasi mobile atau program lain yang berbicara dengan API-mu tidak mengisi formulir HTML. Mereka mengirim datanya sebagai JSON di body request. Untuk menambah produk lewat API, client mengirim sesuatu seperti {"name": "Moka Pot", "price": 320000}, dan request.get_json() mengubahnya menjadi dictionary Python:
from flask import Flask, request
app = Flask(__name__)
PRODUCTS = {}
next_id = 1
@app.post("/api/products")
def create_product():
global next_id
data = request.get_json(silent=True) or {}
name = str(data.get("name", "")).strip()
price = data.get("price")
errors = []
if not name:
errors.append("name is required")
if not isinstance(price, int) or price <= 0:
errors.append("price must be a positive integer")
if errors:
return {"errors": errors}, 400
product = {"id": next_id, "name": name, "price": price}
PRODUCTS[next_id] = product
next_id += 1
return product, 201
client = app.test_client()
r = client.post("/api/products", json={"name": "Matcha 100g", "price": 85000})
print(r.status_code, r.get_json())
r = client.post("/api/products", json={"price": "cheap"})
print(r.status_code, r.get_json())
201 {'id': 1, 'name': 'Matcha 100g', 'price': 85000}
400 {'errors': ['name is required', 'price must be a positive integer']}
client.post(..., json={...})mengirim body JSON (beserta Content-Type yang sesuai).request.get_json(silent=True)mengembalikanNonealih-alih memunculkan error saat body-nya bukan JSON yang valid, sehingga kamu bisa menjawab dengan 400 buatanmu sendiri.- JSON punya tipe sungguhan:
85000datang sebagaiint,"85000"sebagai string. Cek tipenya, bukan hanya nilainya.
Membaca, memperbarui, menghapus
API produk biasanya butuh rangkaian operasi lengkap: melihat satu produk, mengubahnya, dan menghapusnya. Masing-masing memakai URL yang sama, /api/products/<id>, dengan HTTP method berbeda yang menyatakan apa yang harus dilakukan:
from flask import Flask, request, abort
app = Flask(__name__)
PRODUCTS = {1: {"id": 1, "name": "French Press 600ml", "price": 185000}}
def get_or_404(product_id):
return PRODUCTS.get(product_id) or abort(404)
@app.get("/api/products/<int:product_id>")
def read_product(product_id):
return get_or_404(product_id)
@app.patch("/api/products/<int:product_id>")
def update_product(product_id):
product = get_or_404(product_id)
data = request.get_json(silent=True) or {}
if "price" in data:
if not isinstance(data["price"], int) or data["price"] <= 0:
return {"errors": ["price must be a positive integer"]}, 400
product["price"] = data["price"]
return product
@app.delete("/api/products/<int:product_id>")
def delete_product(product_id):
get_or_404(product_id)
del PRODUCTS[product_id]
return "", 204
client = app.test_client()
print(client.patch("/api/products/1", json={"price": 175000}).get_json())
print(client.delete("/api/products/1").status_code)
print(client.get("/api/products/1").status_code)
{'id': 1, 'name': 'French Press 600ml', 'price': 175000}
204
404
204 No Content berarti "selesai, tidak ada yang perlu dikirim balik". Helper kecil seperti get_or_404 membuat setiap route yang butuh "produk ini atau 404" cukup satu baris.
Error JSON, bukan error HTML
Secara default, abort(404) mengembalikan halaman error HTML, yang tidak bisa dipakai client API. Daftarkan error handler yang mengembalikan JSON:
from flask import Flask, abort
app = Flask(__name__)
@app.errorhandler(404)
def not_found(error):
return {"error": "not found"}, 404
@app.get("/api/products/<int:product_id>")
def read_product(product_id):
abort(404)
r = app.test_client().get("/api/products/99")
print(r.status_code, r.mimetype, r.get_json())
404 application/json {'error': 'not found'}
Modul penanganan error membahas ini lebih dalam. Untuk sekarang: API harus menjawab setiap request, termasuk yang gagal, dalam JSON.
API yang baik itu mudah ditebak: kata benda untuk URL, method untuk aksi, JSON masuk dan keluar, status code yang benar, dan error berupa objek JSON dengan pesan yang jelas.
Mengapa ini penting untuk kode yang bersih
API adalah kontrak yang diandalkan kode milik orang lain. Konsistensi (bentuk yang sama, format error yang sama, status code yang sama di mana-mana) lebih penting daripada kecerdikan. Helper kecil (get_or_404, satu fungsi validasi per resource) menjaga setiap route tetap pendek, dan test client memungkinkanmu mengunci kontrak itu dengan test, yang dibahas beberapa modul lagi.
Rangkuman
- REST: kata benda di URL, method HTTP sebagai kata kerja;
GETdaftar/baca,POSTmembuat (201),PATCHmemperbarui,DELETE(204). - Kembalikan dict/list untuk mengirim JSON; bungkus list di dalam objek.
request.get_json(silent=True)membaca body JSON; validasi tipe dan nilainya; jawab 400 dengan semua error.- Gunakan helper
get_or_404untuk "item ini atau 404". - Daftarkan
@app.errorhandler(404)untuk mengembalikan error JSON di API.