Menguasai @app.route untuk memetakan URL ke view function, menangkap variabel path dengan converters int/path/uuid, membatasi HTTP methods GET/POST/PUT/DELETE, dan membangun endpoint CRUD dasar yang mengembalikan JSON.

Di episode 3 kita membuat aplikasi minimal dengan satu route. Sekarang kita memasuki inti dari semua web framework: routing. Setiap URL yang bisa diakses dari aplikasi kalian — /, /users, /posts/42 — adalah hasil dari sistem routing. Kuasai episode ini, dan kalian sudah memahami 80% cara kerja Flask. Kita akan membahas variabel path, converters, HTTP methods, hingga membangun endpoint CRUD dasar — yang menjadi fondasi REST API di episode 14.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index() -> str:
return "Halaman utama"
@app.route("/about")
def about() -> str:
return "Tentang kami"/ menangani root, /about menangani halaman tentang. Flask mencocokkan path URL request ke pattern terdaftar — inilah URL dispatcher-nya. Perhatikan: tanpa method dinyatakan, route menangani semua method, yang jarang kita inginkan.
URL dinamis (/users/42, /posts/my-slug) memakai variabel path dengan syntax <nama>:
@app.route("/users/<username>")
def profile(username: str) -> str:
return f"Profil {username}"
@app.route("/posts/<post_id>")
def post_detail(post_id: str) -> str:
return f"Posting #{post_id}"Variabel di dalam <...> secara otomatis diteruskan sebagai argumen ke fungsi. Tanpa converter, nilainya selalu bertipe str — termasuk angka. Karena itu, untuk data yang memang numerik, gunakan converter.
Converter membatasi dan mengonversi tipe variabel path:
| Converter | Cocok untuk | Contoh |
|---|---|---|
int | Bilangan bulat | <int:user_id> |
path | Path bertingkat (bisa berisi /) | <path:file_path> |
uuid | UUID | <uuid:token> |
string (default) | Tanpa / | <username> |
@app.route("/users/<int:user_id>")
def user_detail(user_id: int) -> str:
return f"User ID {user_id} (tipe {type(user_id).__name__})"
@app.route("/files/<path:file_path>")
def serve_nested(file_path: str) -> str:
return f"Path lengkap: {file_path}"
@app.route("/verify/<uuid:token>")
def verify(token: str) -> str:
return f"Verifikasi token {token}"<int:user_id> melakukan dua hal: menolak URL yang bukan angka (return 404 otomatis) dan mengubah nilainya menjadi int sebelum diteruskan. <path:...> menerima slash (/a/b/c), <uuid:...> hanya menerima UUID valid — konversi gagal otomatis jadi 404.
Tip
Gunakan converter sespesifik mungkin. <int:user_id> langsung membuang request GET /users/abc dengan 404 tanpa perlu validasi manual — lebih aman dan kode kalian lebih pendek. Ini salah satu pola yang langsung terasa manfaatnya di production.
Secara default route menangani semua method. Batasi dengan argumen methods:
from flask import request
@app.route("/items/<int:item_id>", methods=["GET", "PUT", "DELETE"])
def item_ops(item_id: int) -> str:
if request.method == "GET":
return f"Membaca item {item_id}"
if request.method == "PUT":
return f"Memperbarui item {item_id}"
return f"Menghapus item {item_id}"Pola ini sering dipakai, tetapi Flask juga mendukung decorator per-method (@app.get, @app.post, @app.put, @app.delete, @app.patch) sejak 2.0 — lebih eksplisit:
@app.get("/items/<int:item_id>")
def read_item(item_id: int) -> str:
return f"GET item {item_id}"
@app.put("/items/<int:item_id>")
def update_item(item_id: int) -> str:
return f"PUT item {item_id}"
@app.delete("/items/<int:item_id>")
def delete_item(item_id: int) -> str:
return f"DELETE item {item_id}"Menggunakan @app.get dan sejenisnya membuat intent jelas, dan method yang tidak didukung otomatis mendapat 405 Method Not Allowed tanpa perlu if manual.
Sekarang kita rakit semuanya menjadi CRUD dasar memakai data di memori (belum database — itu episode 10):
from flask import Flask, jsonify, request
app = Flask(__name__)
ITEMS: dict[int, str] = {1: "Keyboard", 2: "Mouse"}
@app.get("/items")
def list_items() -> dict:
return jsonify({"items": ITEMS})
@app.get("/items/<int:item_id>")
def get_item(item_id: int) -> tuple[dict, int]:
item = ITEMS.get(item_id)
if item is None:
return jsonify({"error": "item tidak ditemukan"}), 404
return jsonify({"item_id": item_id, "name": item})
@app.post("/items")
def create_item() -> tuple[dict, int]:
data = request.get_json()
if not data or "name" not in data:
return jsonify({"error": "field name wajib"}), 400
new_id = max(ITEMS) + 1
ITEMS[new_id] = data["name"]
return jsonify({"item_id": new_id, "name": data["name"]}), 201
@app.delete("/items/<int:item_id>")
def remove_item(item_id: int) -> tuple[dict, int]:
if item_id not in ITEMS:
return jsonify({"error": "item tidak ditemukan"}), 404
del ITEMS[item_id]
return jsonify({"ok": True})Perhatikan pola tuple[dict, int]: mengembalikan body JSON sekaligus status code (201 untuk created, 400 untuk bad request, 404 untuk not found). Uji dengan curl — misal curl -X POST http://127.0.0.1:5000/items -H "Content-Type: application/json" -d '{"name": "Monitor"}'.
request.get_json() membaca body JSON dari request; jika field wajib tidak ada, kita mengembalikan 400 dengan pesan error — pola validasi paling sederhana yang akan kita sempurnakan di episode 5 dan 12.
Beberapa kesalahan yang sering terjadi:
/items/ dan /items dianggap berbeda. Flask default redirect /items/ ke /items (dan sebaliknya) untuk menambah trailing slash, tetapi konsistensi di kalian lebih baik.methods menerima semua method — kadang GET bisa menghapus data jika view-nya tidak berhati-hati.int converter: string "42" lolos validasi yang seharusnya numerik.Pada episode 4 ini, kalian telah menguasai fondasi routing Flask.
Inti yang harus dibawa pulang:
@app.route("/path/<var>") memetakan URL ke view function; variabel diteruskan sebagai argumen.int, path, uuid) membatasi dan mengonversi nilai — pakai sespesifik mungkin.@app.get, @app.post, dst — 405 otomatis untuk method tak didukung.jsonify() dan pola return jsonify(...), status.Di episode 5 selanjutnya, kita membahas request, response, dan error handling secara utuh — objek request (args, form, json, files), konstruksi Response, redirect, status codes, @app.errorhandler, custom 404/500, dan format error API yang konsisten. Pastikan lab kalian siap, karena episode 5 penuh praktik!