Aplikasi web nyata jarang hanya punya satu halaman, dan datanya jarang hanya datang dari URL. Pengguna mengirim nomor produk lewat path, kata kunci lewat query string, formulir lewat POST, dan data terstruktur lewat JSON. Pada chapter ini kita akan mengatur semua jalur masuk tersebut di Flask — mulai dari route dinamis sampai penanganan error yang rapi — dan sekali lagi menguji semuanya lewat test_client().

Route Dinamis dan Converter #

Bagian <int:produk_id> pada route disebut converter: Flask menangkap potongan URL, mengubahnya ke tipe Python, lalu mengirimkannya sebagai argument fungsi. Perhatikan aplikasi berikut:

python
from flask import Flask, abort, jsonify, redirect, request, url_for

app = Flask(__name__)

PRODUK = {
    1: {"nama": "Buku Python", "harga": 95000},
    2: {"nama": "Mouse Wireless", "harga": 150000},
}


@app.route("/produk/<int:produk_id>")
def detail_produk(produk_id):
    produk = PRODUK.get(produk_id)
    if produk is None:
        abort(404, description=f"Produk {produk_id} tidak ditemukan")
    return jsonify(id=produk_id, **produk)

Flask menyediakan beberapa converter bawaan:

ConverterTipe hasilCocok untukContoh URL
string (default)str tanpa /slug nama/produk/buku-python
intint positifnomor ID/produk/7
pathstr boleh /path berkas/file/laporan/2025/06
uuidUUIDpengenal unik/pesanan/16fd2706-8baf...

Jika nilai URL tidak cocok dengan converter, Flask langsung menjawab 404 — fungsi view tidak pernah dipanggil. Itulah mengapa /produk/tujuh gagal: tujuh bukan bilangan bulat.

Membaca Data dari Request #

Tiga sumber data paling umum — query string, form POST, dan body JSON — semuanya diakses lewat object request:

python
@app.route("/pencarian")
def pencarian():
    kata_kunci = request.args.get("q", "")
    urutan = request.args.get("urutan", "nama")
    return f"Mencari '{kata_kunci}', diurutkan berdasarkan {urutan}"


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        nama = request.form.get("nama", "")
        return f"Selamat datang, {nama}!"
    return "Formulir login (GET)"


@app.route("/api/pesan", methods=["POST"])
def pesan_json():
    data = request.get_json()
    nama = data.get("nama", "tanpa nama")
    isi = data.get("isi", "")
    return jsonify(diterima=True, ringkasan=f"{nama}: {isi}")

Tiga hal penting dari kode di atas:

  1. Baris 3-4 (request.args.get("q", "")): query string pada URL seperti ?q=flask&urutan=harga dibaca seperti dictionary; argument kedua menjadi nilai default bila kunci tidak ada.
  2. Baris 8 (methods=["GET", "POST"]): tanpa parameter ini, route hanya menerima GET. Method lain ditolak dengan 405 Method Not Allowed.
  3. Baris 10 (request.method == "POST"): satu fungsi menangani dua method sekaligus — umum untuk pola formulir: GET menampilkan form, POST memprosesnya.
  4. Baris 18 (request.get_json()): body JSON di-parse menjadi dictionary; klien wajib mengirim header Content-Type: application/json.

redirect, url_for, dan Error Handler #

Halaman yang pindah alamat dan request yang gagal butuh jawaban yang sopan. Fungsi url_for() membangun URL dari nama endpoint (secara default nama fungsi), sehingga kita bebas mengubah path tanpa menyusuri seluruh templates mencari teks URL:

python
@app.route("/ke-produk-1")
def ke_produk_1():
    return redirect(url_for("detail_produk", produk_id=1))


@app.errorhandler(404)
def tidak_ditemukan(error):
    return jsonify(error=str(error.description)), 404

errorhandler mencegat semua error 404 di aplikasi — termasuk yang dipicu abort() pada route produk — dan menggantinya dengan format response milik kita.

Menguji Semuanya Sekaligus #

python
client = app.test_client()

print(client.get("/produk/2").status_code, client.get("/produk/2").get_json())
print(client.get("/produk/tujuh").status_code)
print(client.get("/produk/99").status_code, client.get("/produk/99").get_json())
print(client.get("/pencarian?q=flask&urutan=harga").text)
print(client.get("/pencarian").text)
print(client.post("/login", data={"nama": "Budi"}).text)
r = client.post("/api/pesan", json={"nama": "Siti", "isi": "Halo semua"})
print(r.status_code, r.get_json())
r = client.get("/ke-produk-1")
print(r.status_code, r.headers.get("Location"))
print(client.get("/ke-produk-1", follow_redirects=True).get_json())

Output yang Dihasilkan:

text
200 {'harga': 150000, 'id': 2, 'nama': 'Mouse Wireless'}
404
404 {'error': 'Produk 99 tidak ditemukan'}
Mencari 'flask', diurutkan berdasarkan harga
Mencari '', diurutkan berdasarkan nama
Selamat datang, Budi!
200 {'diterima': True, 'ringkasan': 'Siti: Halo semua'}
302 /produk/1
{'harga': 95000, 'id': 1, 'nama': 'Buku Python'}

Baris kedua memperlihatkan converter bekerja (tujuh ditolak), baris ketiga memperlihatkan abort() yang ditangkap errorhandler, dan dua baris terakhir memperlihatkan redirect: status 302 dengan header Location, lalu versi follow_redirects=True yang mengikuti alamat baru sampai datanya tercetak.