Handbook Pembuatan Web Geprek Kita
Haikal Maghsin · NIM 254107020189 · TI 2D
Desain dan Pemrograman Web · Panduan praktik untuk UTS
Edisi revisi · 27 September 2026
Panduan ini mengikuti urutan membangun dan memahami aplikasi: persiapan, struktur halaman, desain, interaksi, database, dan proses transaksi. Buka proyek sambil membaca, jalankan setiap langkah, lalu cocokkan hasilnya.
Potongan kode merupakan bagian penting dari implementasi, bukan pengganti semua isi file. Gunakan berkas sumber yang disebutkan untuk melihat kode lengkap. Bila kode sudah ada, pelajari dan ubah salinan latihan; jangan menempel ulang potongan hingga menjadi duplikat.
Cara menggunakan handbook
- Baca tujuan dan langkah kerja setiap bab.
- Buka file yang dirujuk.
- Ikuti penjelasan kode dan perubahan kecil yang disarankan.
- Periksa hasil melalui browser.
- Catat pengujian aktual pada bab terakhir.
Screenshot diambil dari aplikasi lokal saat revisi. Screenshot membuktikan tampilan yang terlihat, bukan bukti seluruh operasi simpan, edit, dan hapus sudah diuji.
Bab 1 — Mengenal aplikasi
Tujuan: memahami kebutuhan sebelum menulis kode.
Langkah kerja
- Tentukan pengguna aplikasi: petugas yang mencatat pelanggan dan pesanan.
- Tentukan tiga halaman inti: Menu, Anggota, dan Pesanan.
- Tentukan data: identitas pelanggan, pilihan menu, harga, dan jumlah.
- Batasi fitur agar proyek tetap mudah dijelaskan.
Hasil tampilan awal

Gambar 1. Halaman menu menampilkan tiga pilihan makanan dan tautan pemesanan.
Geprek Kita mencatat anggota pelanggan dan pesanan makanan. Aplikasi menggunakan HTML, CSS, JavaScript, PHP, dan PostgreSQL. Supabase menyediakan database PostgreSQL yang dihubungi PHP melalui PDO.
Repository terkait: PRAKDPWeb26_2D_Haikal-Maghsin_Deploy.
Handbook disusun dari berkas lokal terbaru yang terhubung ke repository tersebut. Akses GitHub belum berhasil diverifikasi saat penyusunan, dan perubahan lokal belum tentu sudah di-push. Jadi penjelasan mengacu pada kode lokal, bukan klaim bahwa semua isinya sudah tersedia di GitHub.
Fitur yang ada:
- Menampilkan tiga menu dan harga.
- Menambah, melihat, mengedit, menghapus, dan mencari anggota.
- Menambah, melihat, mengedit, dan menghapus pesanan.
- Menghitung perkiraan total di browser.
- Memvalidasi input di browser dan di PHP.
- Menyimpan anggota dan pesanan ke database jika koneksi dan tabel sudah siap.
Satu pesanan memuat satu jenis menu. Proyek belum menyediakan login, pembayaran, keranjang banyak menu, atau pengelolaan stok. Menu disimpan sebagai array PHP, bukan tabel database.
Status saat revisi: halaman pesanan lokal menampilkan data tanpa pesan koneksi gagal. Ini menunjukkan pembacaan data berjalan pada sesi tersebut. Operasi tambah, edit, dan hapus belum diuji ulang saat revisi handbook.
Bab 2 — Menyiapkan proyek dan server
Tujuan: dapat membuka proyek melalui PHP, bukan dengan klik langsung file PHP.
Langkah kerja
- Buka folder repo deploy di VS Code.
- Periksa struktur file di bawah.
- Siapkan PHP, ekstensi database, dan konfigurasi lokal.
- Jalankan server dan buka /prak8/.
- Pastikan daftar menu muncul sebelum melanjutkan.
Alamat yang dibuka pengguna tidak selalu sama dengan letak file. Rute /prak8/ diarahkan ke kode di folder api/.
| Alamat / file | Kegunaan |
|---|---|
/ → index.html | Masterpage untuk membuka praktikum 1–8 |
/prak8/index.php → prak8/index.php | Halaman menu Geprek Kita |
prak8/includes/header.php | Bagian atas, navigasi, fungsi esc |
prak8/includes/footer.php | Bagian bawah dan pemanggilan JavaScript |
prak8/includes/menu.php | Array nama menu dan harga |
prak8/includes/env.php | Membaca konfigurasi lokal dari .env |
prak8/includes/koneksi.php | Membuat koneksi PDO ke PostgreSQL |
prak8/includes/session.php | Memulai session untuk pesan sementara |
prak8/anggota/list.php | Membaca dan menampilkan anggota |
prak8/anggota/tambah.php | Form tambah anggota |
prak8/anggota/proses_tambah.php | Validasi dan INSERT anggota |
prak8/anggota/edit.php | Mengambil anggota berdasarkan ID untuk form edit |
prak8/anggota/proses_edit.php | UPDATE atau DELETE anggota |
prak8/pesanan/list.php | Form, daftar, tambah, edit, dan hapus pesanan |
assets/css/style.css | CSS aplikasi Geprek Kita |
assets/js/app.js | Validasi, konfirmasi, pencarian, dan total |
sql/02_geprek.sql | Struktur tabel anggota dan pesanan |
router.php | Pengaturan alamat ketika server PHP lokal dijalankan |
vercel.json | Pengaturan rute dan fungsi PHP untuk deployment |
Masterpage memakai assets/css/masterpage.css, bukan CSS aplikasi. Repo praktikum berisi sumber latihan; repo deploy ini berisi masterpage dan salinan latihan yang siap ditampilkan.
Session dipakai untuk pesan sementara setelah redirect, misalnya “Anggota berhasil ditambahkan.” Data anggota dan pesanan tetap berada di PostgreSQL, bukan session. Session sementara di hosting serverless tidak dijamin tersedia pada instance lain; jangan menjadikannya penyimpanan utama.
Menjalankan lokal
- Pastikan PHP tersedia dan ekstensi pdo_pgsql aktif.
- Siapkan .env dari .env.example; isi kredensial database sendiri.
- Jalankan isi sql/02_geprek.sql di SQL Editor database yang dipakai.
- Buka terminal di folder repo deploy.
- Jalankan perintah berikut.
php -S localhost:8000 router.php
Jika PHP belum masuk PATH pada komputer ini:
& 'C:\xampp\php\php.exe' -S localhost:8000 router.php
Buka http://localhost:8000/prak8/. Alamat http://localhost:8000/ adalah masterpage. Proyek ini tidak membutuhkan npm run dev.
Arti konfigurasi
Contoh bentuk isi .env berikut hanya memakai placeholder. Ganti dengan nilai koneksi milik sendiri. Bila memakai database Supabase bernama postgres, jangan mengganti DB_NAME hanya karena judul aplikasi berubah.
DB_HOST=alamat_server_database
DB_PORT=5432
DB_NAME=postgres
DB_USER=username_database
DB_PASS="password_database_sendiri"
DB_SSLMODE=require
Di terminal, perintah php -m menampilkan ekstensi aktif. Cari pdo_pgsql. Jika tidak ada, aktifkan ekstensi itu pada php.ini milik PHP yang sedang dijalankan, lalu restart server PHP.
| Variabel | Isi |
|---|---|
| DB_HOST | Alamat server database |
| DB_PORT | Port koneksi |
| DB_NAME | Nama database, bukan connection string lengkap |
| DB_USER | Username database |
| DB_PASS | Password database, bukan password akun dashboard |
| DB_SSLMODE | Pengaturan SSL; ikuti konfigurasi koneksi yang digunakan |
| DATABASE_URL | Alternatif URI koneksi lengkap |
Kode memprioritaskan DATABASE_URL jika terisi. Jika tidak, kode membaca variabel DB_*. Loader .env tidak menimpa variabel yang sudah tersedia di environment proses/server.
.env tidak boleh dimasukkan ke handbook atau Git. Pada hosting, isi Environment Variables yang diperlukan. Mengubah file lokal tidak otomatis memperbarui konfigurasi deployment.
Bab 3 — Membuat kerangka halaman dan menu
File utama: prak8/includes/header.php, prak8/includes/footer.php, prak8/includes/menu.php, prak8/index.php.
Tujuan: memahami bagaimana satu kerangka dipakai banyak halaman.
Langkah kerja
- Letakkan pembuka HTML, judul, CSS, header, dan navigasi di header.php.
- Letakkan penutup halaman dan script di footer.php.
- Tulis array menu di menu.php.
- Muat header dan menu melalui index.php.
- Gunakan foreach untuk menghasilkan baris tabel.
$page_title = 'Menu';
include __DIR__ . '/includes/header.php';
require __DIR__ . '/includes/menu.php';
page_title memberi nama halaman. include memuat kerangka. require memuat menu yang dibutuhkan. __DIR__ membuat lokasi file dihitung dari folder file PHP, bukan dari alamat browser.
foreach ($daftarMenu as $menu) {
echo '<tr><td>' . esc($menu['nama']) . '</td></tr>';
}
Potongan di atas adalah contoh minimal untuk memahami perulangan. Implementasi lengkap di index.php juga menampilkan harga dengan number_format.
Periksa hasil: tiga baris menu muncul. Ubah satu nama pada menu.php, refresh, dan pastikan perubahan terlihat.
| Kode | Makna |
|---|---|
| $nama | Variabel PHP |
| $_POST['nama'] | Nilai nama dari form POST |
| $_GET['id'] | Nilai id dari URL |
| ?? | Nilai pengganti jika data tidak ada atau null |
| trim() | Menghapus spasi di awal dan akhir |
| if / else | Memilih tindakan berdasarkan kondisi |
| foreach | Mengulang setiap item array |
| require / include | Memuat file PHP lain |
| require_once | Memuat file wajib sekali saja |
| __DIR__ | Folder tempat file PHP itu berada |
| header('Location: ...') | Mengarahkan browser ke alamat lain |
| exit | Menghentikan eksekusi setelah redirect |
| try / catch | Menangani kegagalan melalui exception |
Contoh membaca form:
$nama = trim($_POST['nama'] ?? '');
Jika nama belum dikirim, gunakan string kosong supaya tidak membaca indeks yang tidak tersedia.
Menu menggunakan array asosiatif:
$daftarMenu = [
'original' => ['nama' => 'Geprek Original', 'harga' => 15000],
'keju' => ['nama' => 'Geprek Keju', 'harga' => 18000],
'sambal_ijo' => ['nama' => 'Geprek Sambal Ijo', 'harga' => 16000],
];
Kode seperti original menjadi kunci menu. Angka harga tidak memakai titik ribuan. Titik pemisah ditambahkan saat ditampilkan.
Fungsi esc() di header memakai htmlspecialchars(..., ENT_QUOTES, 'UTF-8'). Tujuannya membuat isi pengguna ditampilkan sebagai teks HTML, bukan dianggap markup. Ini berbeda dari prepared statement yang melindungi pemakaian nilai dalam SQL.
Bab 4 — Membuat form dan tabel HTML
File utama: prak8/anggota/tambah.php dan prak8/pesanan/list.php.
Tujuan: menghubungkan input yang terlihat dengan data yang dikirim.
Langkah kerja
- Buat form anggota berisi nama, nomor anggota, alamat, dan nomor HP.
- Beri setiap input label, id, dan name yang sesuai.
- Gunakan method POST serta action proses_tambah.php pada form anggota.
- Buat form pesanan dengan select anggota, select menu, dan input jumlah.
- Siapkan tabel untuk menampilkan hasil.

Gambar 2. Form anggota sebelum diisi. Nama input menjadi kunci data pada PHP.
Periksa hasil: klik label untuk memfokuskan input; gunakan Tab untuk berpindah kolom.
Menghubungkan form dengan PHP
Berikut contoh minimal pola form anggota. Form lengkap di tambah.php juga mempunyai nomor anggota, alamat, dan nomor HP.
<form method="post" action="proses_tambah.php"
data-validate="true" novalidate>
<label for="nama">Nama</label>
<input type="text" id="nama" name="nama" required autofocus>
<button type="submit">Simpan anggota</button>
</form>
- Browser membuka tambah.php dan menampilkan form.
- Ketika tombol Simpan ditekan, JavaScript memeriksa isian.
- Jika lolos, action mengirim form ke proses_tambah.php dalam folder yang sama.
- PHP membaca nilai melalui
$_POST['nama'].
Pada form pesanan, action tidak ditulis. Browser mengirim form kembali ke halaman pesanan yang sedang dibuka. Karena itu, pengolahan POST ditempatkan di bagian atas pesanan/list.php, sebelum HTML dikirim.
Buka prak8/pesanan/list.php dan prak8/includes/header.php.
| Elemen / atribut | Penjelasan sederhana |
|---|---|
| header, nav, main, footer | Membagi halaman menurut fungsinya |
| section | Mengelompokkan bagian isi |
| table, thead, tbody, tr, th, td | Menyusun tabel dan baris data |
| form method="post" | Mengirim isian dalam badan permintaan HTTP |
| label for="jumlah" | Menghubungkan judul kolom dengan input ber-ID jumlah |
| name="jumlah" | Nama data yang dibaca PHP dari kiriman form |
| id="jumlah" | Penanda elemen untuk label, CSS, atau JavaScript |
| required | Menandai kolom wajib |
| autofocus | Meminta browser memfokuskan kolom saat halaman dibuka |
| min, max, step | Batas angka dan langkah nilai yang diizinkan |
| input type="hidden" | Mengirim ID/aksi tanpa menampilkan kotak input |
| data-harga | Menyimpan harga pada option untuk dibaca JavaScript |
Contoh input dari aplikasi:
<input type="number" id="jumlah" name="jumlah"
min="1" max="100" step="1" required>
Artinya: jumlah wajib diisi bilangan bulat antara 1 dan 100. Pada aplikasi, atribut value juga diisi dari PHP supaya isian edit tampil kembali.
Form menggunakan novalidate agar pesan validasi otomatis saat submit digantikan oleh pesan JavaScript buatan sendiri. JavaScript tetap memanggil checkValidity() untuk membaca aturan input.
Penting: hidden input bukan tempat menyembunyikan rahasia. Nilainya tetap bisa dilihat dan diubah melalui browser. POST juga tidak otomatis membuat data aman; HTTPS mengenkripsi pengiriman.
Bab 5 — Menata halaman dengan CSS
File utama: assets/css/style.css.
Tujuan: memahami setiap aturan visual yang digunakan.
Langkah kerja
- Atur font, warna teks, dan background pada body.
- Gunakan flex pada header agar nama warung dan navigasi berjajar.
- Beri main batas lebar dan panel ruang dalam.
- Atur input, tombol, link, dan tabel.
- Tambahkan hover dan penanda fokus.
- Tambahkan media query agar header ditumpuk di HP.
Latihan: ubah padding tombol dari 7px 12px menjadi 9px 16px. Amati perubahan tinggi dan lebar. Kembalikan jika terlalu besar.
Periksa hasil: pada layar sempit, navigasi tetap terbaca dan tabel bisa digeser.
Buka assets/css/style.css. Pola CSS adalah selector, properti, dan nilai.
nav a {
color: white;
background: #496374;
}
nav a memilih link di dalam navigasi. color mengatur warna tulisan. background mengatur latar link.
| Bagian CSS | Kegunaan dalam proyek |
|---|---|
| box-sizing: border-box | Padding dan border ikut diperhitungkan dalam width |
| font-family | Jenis huruf; Georgia untuk nama warung |
| font-size | Besar tulisan |
| line-height | Tinggi baris tulisan |
| padding | Jarak isi dengan tepi dalam kotak |
| margin | Jarak di luar kotak |
| border | Garis pinggir |
| border-radius | Sudut membulat |
| max-width: 900px | Membatasi lebar konten |
| margin: 24px auto | Jarak atas-bawah 24px, posisi horizontal di tengah |
| display: flex | Mengatur anak elemen dalam susunan fleksibel |
| gap | Jarak antar-item flex atau grid |
| overflow-x: auto | Membolehkan tabel digeser horizontal |
| :hover | Tampilan ketika pointer berada di elemen |
| :focus-visible | Penanda fokus, misalnya saat memakai keyboard |
| @media | Aturan yang berlaku pada ukuran layar tertentu |
Mengubah ukuran tombol
Cari selector button, .btn-tambah, .btn-aksi.
padding: 7px 12px;
font: inherit;
line-height: 1.4;
7px adalah jarak atas-bawah, 12px jarak kiri-kanan. Ubah menjadi 9px 16px untuk tombol lebih besar. font: inherit mengikuti font induknya. Jika ingin menentukan ukuran khusus, tambahkan font-size: 15px setelah font: inherit.
Kalau hanya tombol Edit dan Hapus yang ingin diubah, buat aturan khusus .btn-aksi. Jangan mengubah semua tombol jika kebutuhan hanya pada tabel.
Mengubah warna dan bentuk link
Ubah background pada selector a untuk link umum, atau nav a untuk navigasi. Selector navigasi lebih spesifik. Aturan nav a[aria-current="page"] memberi warna berbeda pada halaman aktif.
Tampilan HP
@media (max-width: 600px) {
header { flex-direction: column; align-items: flex-start; }
.panel { padding: 14px; }
}
Saat lebar layar maksimal 600px, isi header ditumpuk dan ruang dalam panel dikurangi. Tabel memakai pembungkus dengan overflow-x: auto supaya tetap bisa dibaca di layar kecil.
Bab 6 — Menambahkan interaksi JavaScript
File utama: assets/js/app.js.
Tujuan: membuat form mudah digunakan sebelum data dikirim ke server.
Langkah kerja
- Hubungkan app.js melalui footer agar elemen HTML sudah tersedia.
- Pasang event submit untuk konfirmasi hapus.
- Periksa kolom wajib dan batasi input angka.
- Tampilkan pesan serta arahkan fokus ke kolom salah pertama.
- Hubungkan perubahan menu dan jumlah ke fungsi hitungTotal.
- Tambahkan pencarian anggota.
Periksa hasil: form kosong menampilkan pesan, memilih menu mengubah total, dan Batal pada konfirmasi tidak mengirim penghapusan.
Buka assets/js/app.js.
A. Konfirmasi hapus
querySelectorAll(".form-hapus") mengambil semua form hapus. addEventListener("submit", ...) menunggu form dikirim. confirm() menampilkan pertanyaan. Jika pengguna memilih Batal, event.preventDefault() menghentikan submit.
Konfirmasi ini membantu menghindari salah klik; penghapusan sebenarnya dilakukan PHP dan SQL.
B. Validasi dan fokus kursor
Urutan kode:
- Ambil form dengan atribut
data-validate. - Saat submit, periksa input dan select.
- Gunakan
trim()agar isian spasi saja dianggap kosong. - Buat elemen span berisi pesan “Form ini wajib diisi.”
- Tandai input dengan
aria-invaliddan hubungkan pesan melaluiaria-describedby. - Batalkan submit jika salah, lalu panggil
focus()pada input salah pertama.
Saat pengguna mengetik lagi, pesan sebelumnya dihapus. Validasi JavaScript membantu pengguna, tetapi bisa dilewati; PHP tetap harus memeriksa input.

Gambar 3. Setelah form kosong dikirim, empat pesan wajib diisi muncul dan fokus kembali ke kolom Nama. Screenshot berasal dari uji browser lokal tanpa menambah data database.
C. Menghitung total
Potongan asli dari fungsi hitungTotal():
const harga = Number(menu.selectedOptions[0].dataset.harga || 0);
const total = harga * Number(jumlah.value);
document.getElementById("total").textContent =
"Rp " + total.toLocaleString("id-ID");
selectedOptions[0] adalah pilihan menu aktif. dataset.harga membaca data-harga. Number() mengubah nilai menjadi angka. textContent menampilkan teks. Format id-ID memberi pemisah angka sesuai Indonesia.
Event change dipakai saat menu berganti; event input dipakai saat jumlah berubah.
D. Pencarian anggota
Kode membaca teks setiap baris tabel, mengubahnya menjadi huruf kecil, lalu memeriksa includes(). Baris yang tidak cocok diberi hidden = true.
Pencarian ini menyaring data yang sudah tampil di browser. Ia tidak mengirim query pencarian baru ke database.
Bab 7 — Menyiapkan database dan koneksi PHP
File utama: sql/02_geprek.sql, prak8/includes/env.php, prak8/includes/koneksi.php.
Tujuan: menghubungkan data anggota dengan pesanannya.
Langkah kerja
- Buka SQL Editor pada database yang akan dipakai.
- Jalankan isi lengkap sql/02_geprek.sql.
- Pastikan tabel anggota dan pesanan terbentuk.
- Isi konfigurasi .env dengan kredensial sendiri.
- Periksa bahwa pdo_pgsql tersedia.
- Buka halaman Anggota atau Pesanan untuk mencoba pembacaan data.
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
dsn berisi jenis database, host, port, nama database, dan mode SSL. user dan pass berisi kredensial dari environment. Mode exception membuat kegagalan dapat ditangani oleh try/catch.
Kode koneksi memilih DATABASE_URL jika tersedia; jika tidak, variabel DB_* dipakai. Jangan menulis password langsung dalam handbook atau file kode yang diunggah.
Periksa hasil: halaman membaca data atau menampilkan kondisi kosong tanpa pesan gagal koneksi. Berhasil menampilkan halaman Menu saja belum membuktikan database tersambung karena halaman itu membaca array PHP.
Struktur resmi proyek ada di sql/02_geprek.sql.
Membuat kedua tabel
Jalankan perintah ini melalui SQL Editor pada database yang digunakan aplikasi. Isinya mengikuti skema proyek:
CREATE TABLE IF NOT EXISTS anggota (
id SERIAL PRIMARY KEY,
nama VARCHAR(255) NOT NULL,
no_anggota VARCHAR(50) NOT NULL UNIQUE,
alamat VARCHAR(255),
no_hp VARCHAR(30)
);
CREATE TABLE IF NOT EXISTS pesanan (
id SERIAL PRIMARY KEY,
anggota_id INTEGER NOT NULL REFERENCES anggota(id) ON DELETE RESTRICT,
menu VARCHAR(50) NOT NULL,
harga INTEGER NOT NULL CHECK (harga > 0),
jumlah INTEGER NOT NULL CHECK (jumlah BETWEEN 1 AND 100),
dibuat_pada TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Buat anggota terlebih dahulu karena pesanan merujuk ID anggota. SERIAL menyediakan nomor otomatis, NOT NULL mewajibkan nilai, UNIQUE mencegah nomor anggota kembar, dan CHECK membatasi nilai. IF NOT EXISTS menghindari pembuatan ulang tabel yang sudah ada; perintah ini tidak memperbarui struktur tabel lama secara otomatis.
anggota pesanan
id (primary key) 1 ──────── N anggota_id (foreign key)
nama id (primary key)
no_anggota (unique) menu
alamat harga
no_hp jumlah
dibuat_pada
Satu anggota bisa memiliki banyak pesanan. Setiap pesanan menunjuk satu anggota.
| Kolom | Tipe / aturan |
|---|---|
| anggota.id | SERIAL, primary key |
| anggota.nama | VARCHAR(255), wajib di database |
| anggota.no_anggota | VARCHAR(50), wajib dan unik |
| anggota.alamat | VARCHAR(255), database mengizinkan null |
| anggota.no_hp | VARCHAR(30), database mengizinkan null |
| pesanan.id | SERIAL, primary key |
| pesanan.anggota_id | INTEGER, wajib, foreign key ke anggota |
| pesanan.menu | VARCHAR(50), kode menu |
| pesanan.harga | INTEGER, harus lebih dari 0 |
| pesanan.jumlah | INTEGER, antara 1 dan 100 |
| pesanan.dibuat_pada | TIMESTAMP, otomatis memakai waktu saat insert |
Form dan PHP mewajibkan alamat serta nomor HP, meskipun tabel database tidak menggunakan NOT NULL pada kedua kolom itu. Jadi aturan aplikasi lebih ketat daripada skema pada bagian ini.
ON DELETE RESTRICT mencegah anggota dihapus saat masih dirujuk oleh pesanan. Hapus pesanan terkait terlebih dahulu jika memang ingin menghapus anggota latihan.
Total tidak menjadi kolom tersendiri: dihitung dari harga × jumlah. Harga disimpan pada pesanan. Mengubah array harga tidak langsung mengubah pesanan lama, tetapi menyimpan edit pesanan akan mengambil harga menu yang berlaku saat edit.
Bab 8 — Memproses tambah, edit, dan hapus
File utama: prak8/anggota/proses_tambah.php, prak8/anggota/proses_edit.php, prak8/pesanan/list.php.
Tujuan: mengikuti satu transaksi dari form sampai ke database.
Langkah kerja: anggota
- Ambil POST dan rapikan spasi dengan trim.
- Periksa semua kolom wajib.
- Siapkan INSERT menggunakan placeholder.
- Jalankan execute dan tangani nomor anggota yang duplikat.
- Simpan pesan sementara, redirect, lalu tampilkan daftar.
- Untuk edit, gunakan ID agar UPDATE hanya mengubah anggota yang dipilih.
- Untuk hapus, kirim aksi=hapus melalui POST.
Langkah kerja: pesanan
- Baca anggota_id, menu, dan jumlah dari POST.
- Validasi ID, kode menu, dan jumlah bulat 1–100.
- Ambil harga dari array PHP, bukan total kiriman browser.
- Gunakan INSERT jika ID kosong atau UPDATE jika mengedit.
- Gunakan DELETE ketika aksi adalah hapus.
- Jalankan SELECT dengan JOIN untuk menampilkan nama anggota.

Gambar 4. Tampilan pesanan aktual saat revisi. Data yang sudah ada hanya dibaca; tidak diubah untuk membuat ilustrasi.
Periksa hasil: setelah koneksi siap, gunakan data khusus latihan untuk memeriksa tambah, edit, dan hapus.
CRUD adalah Create, Read, Update, Delete.
Menelusuri validasi sebelum INSERT
Potongan dari pesanan/list.php:
$anggotaId = filter_input(INPUT_POST, 'anggota_id', FILTER_VALIDATE_INT);
$kode = $_POST['menu'] ?? '';
$jumlah = filter_input(INPUT_POST, 'jumlah', FILTER_VALIDATE_INT);
if (!$anggotaId || !isset($daftarMenu[$kode]) ||
!$jumlah || $jumlah < 1 || $jumlah > 100) {
throw new RuntimeException('Pilih anggota, menu, dan jumlah antara 1 sampai 100.');
}
$menu = $daftarMenu[$kode];
filter_input memeriksa apakah nilai berupa bilangan bulat. isset memastikan kode menu dikenali. Jika salah, exception menghentikan jalur simpan dan ditangkap oleh catch. ID anggota juga harus benar-benar ada; hubungan foreign key diperiksa database ketika query dijalankan.
Mengisi form edit dan memperbarui data
Pada anggota/edit.php, ID dari URL dipakai untuk mengambil data:
$stmt = $pdo->prepare('SELECT * FROM anggota WHERE id = :id');
$stmt->execute(['id' => $id]);
$anggota = $stmt->fetch(PDO::FETCH_ASSOC);
fetch mengambil satu baris sebagai array dengan nama kolom sebagai kunci. Form mengisi value menggunakan data itu, misalnya esc($anggota['nama']). ID dimasukkan ke hidden input untuk dikirim kembali saat menyimpan.
Untuk pesanan, query pembaruannya adalah:
$stmt = $pdo->prepare(
'UPDATE pesanan SET anggota_id = ?, menu = ?, harga = ?, jumlah = ? WHERE id = ?'
);
$stmt->execute([$anggotaId, $kode, $menu['harga'], $jumlah, $id]);
SET berisi kolom yang diganti. WHERE menentukan pesanan yang diubah. Urutan lima nilai pada execute harus sama dengan urutan lima placeholder dalam query.
| Operasi | SQL | Contoh |
|---|---|---|
| Create | INSERT | Menambah anggota atau pesanan |
| Read | SELECT | Mengisi tabel dan pilihan anggota |
| Update | UPDATE | Mengubah pesanan terpilih |
| Delete | DELETE | Menghapus berdasarkan ID |
Contoh asli penambahan pesanan:
$stmt = $pdo->prepare(
'INSERT INTO pesanan (anggota_id, menu, harga, jumlah) VALUES (?, ?, ?, ?)'
);
$stmt->execute([$anggotaId, $kode, $menu['harga'], $jumlah]);
prepare() menyiapkan SQL dengan placeholder. execute() memasukkan nilai sesuai urutan. Nilai input dipisahkan dari struktur SQL sehingga tidak digabung langsung menjadi perintah SQL.
Contoh penghapusan:
$stmt = $pdo->prepare('DELETE FROM pesanan WHERE id = ?');
$stmt->execute([$id]);
WHERE id = ? menentukan baris yang dihapus. Tanpa WHERE, DELETE dapat menghapus seluruh isi tabel. Jangan mencoba query tanpa kondisi pada data yang masih dibutuhkan.
Query daftar pesanan:
SELECT p.*, a.nama
FROM pesanan p
JOIN anggota a ON a.id = p.anggota_id
ORDER BY p.id DESC
p dan a adalah alias tabel. JOIN mengambil nama anggota agar daftar pesanan tidak hanya menampilkan nomor ID. DESC menampilkan ID terbesar terlebih dahulu.
Jalur tambah, edit, dan hapus pesanan
- Tambah: POST dengan ID kosong → validasi → INSERT.
- Buka edit: GET
?id=...→ isi form dari pesanan yang cocok. - Simpan edit: POST dengan ID → validasi → UPDATE.
- Hapus: POST dengan
aksi=hapusdan ID → DELETE. - Berhasil: simpan pesan session → redirect ke list.php.
Redirect setelah POST mengurangi risiko form terkirim ulang ketika halaman hasil di-refresh.
Alur menyeluruh
Browser membuka halaman
↓
PHP membaca menu / database
↓
HTML dikirim ke browser
↓
Pengguna mengisi form
↓
JavaScript memeriksa input dan menampilkan total
↓
Form dikirim melalui POST
↓
PHP memeriksa input lagi
↓
PDO menjalankan SQL ke PostgreSQL
↓
Redirect → halaman daftar menampilkan data terbaru
JavaScript berjalan di browser. PHP berjalan di server. PostgreSQL menyimpan data. Ketiganya punya tugas berbeda.
Contoh: pengguna memilih Geprek Original sebanyak 2 porsi. Browser menampilkan Rp 30.000. PHP mengambil harga Original dari array menu, lalu menyimpan harga 15.000 dan jumlah 2. Total pada daftar dihitung dari harga tersimpan dikali jumlah.
Bab 9 — Pengujian dan perbaikan
Tujuan: mencatat bukti hasil, bukan hanya menyiapkan daftar skenario.
Hasil pemeriksaan saat revisi
| Pemeriksaan | Hasil aktual |
|---|---|
| Halaman Menu lokal | Terbuka dan menampilkan daftar menu |
| Halaman Tambah Anggota lokal | Form terlihat |
| Halaman Pesanan lokal | Daftar pesanan terlihat tanpa pesan gagal koneksi |
| Submit form anggota kosong | Empat pesan validasi muncul; fokus kembali ke Nama |
| Perkiraan harga Original × 2 | JavaScript menampilkan Rp 30.000 |
| Jumlah 0, 101, dan 1.5 | checkValidity di browser mengembalikan false |
| Tambah, edit, dan hapus database | Belum diuji ulang pada revisi dokumentasi |
Langkah kerja
- Jalankan skenario dengan data latihan.
- Catat hasil aktual, tanggal, dan jika perlu screenshot.
- Bandingkan dengan hasil yang diharapkan.
- Jika gagal, periksa lapisan yang terkait: browser, PHP, atau database.
- Perbaiki penyebab lalu ulangi skenario yang gagal.
Gunakan data khusus latihan, bukan data penting. Isi hasil aktual setelah mencoba; daftar ini adalah skenario uji, bukan catatan uji yang sudah lulus.
- Buka masterpage dan Launch Praktikum 8.
- Jelaskan daftar menu, anggota, dan pesanan.
- Tambah anggota “Pelanggan Uji UTS” dengan nomor unik, alamat, dan nomor HP contoh.
- Cari anggota tersebut melalui kotak pencarian.
- Edit nama, simpan, lalu periksa tabel.
- Pesan Geprek Original sebanyak 2 porsi; total yang diharapkan Rp 30.000.
- Simpan, refresh halaman, lalu periksa apakah pesanan tetap tampil dari database.
- Edit jumlah menjadi 3; total yang diharapkan Rp 45.000.
- Coba hapus anggota yang masih punya pesanan; operasi seharusnya ditolak.
- Klik Hapus pesanan, pilih Batal; baris harus tetap ada.
- Hapus pesanan dengan konfirmasi, lalu hapus anggota latihan.
- Tunjukkan validasi form kosong dan batas jumlah 1–100.
| Pengujian | Hasil yang diharapkan | Hasil aktual |
|---|---|---|
| Form wajib kosong | Pesan muncul, fokus pindah ke kolom salah | Lulus di form anggota pada 27 September 2026 |
| Jumlah 0 / 101 / pecahan | Ditolak validasi | Lulus pemeriksaan browser; penolakan POST langsung belum diuji |
| Nomor anggota duplikat | Pesan nomor sudah digunakan | Belum diisi |
| Refresh setelah simpan | Tidak menggandakan data dari POST sebelumnya | Belum diisi |
| Ubah menu/jumlah | Perkiraan total ikut berubah | Original × 2 menampilkan Rp 30.000 pada 27 September 2026 |
| Hapus anggota yang dirujuk pesanan | Ditolak database | Belum diisi |
Jangan mengaku pengujian berhasil sebelum melihat hasilnya. Saat revisi, pembacaan data terlihat berhasil. Operasi tulis tetap perlu dibuktikan dengan pengujian terpisah.
Menangani masalah
| Gejala | Yang diperiksa |
|---|---|
| Data warung belum bisa dimuat | Koneksi gagal; periksa konfigurasi dan penyebab melalui pemeriksaan lokal yang tidak membocorkan kredensial |
| password authentication failed | Password database tidak cocok dengan pengguna/server yang dituju |
| Tabel pesanan belum tersedia | Jalankan skema di database yang benar |
| could not find driver | Aktifkan pdo_pgsql pada PHP yang sedang dijalankan |
| Nomor anggota sudah digunakan | Gunakan nomor berbeda; kolom no_anggota bersifat unik |
| Anggota gagal dihapus | Periksa apakah masih memiliki pesanan |
| Tombol simpan pesanan nonaktif | Pastikan koneksi tersedia dan anggota sudah ada |
| CSS belum berubah | Periksa file CSS yang tepat lalu lakukan hard refresh |
| PHP terbaca sebagai teks | Jalankan melalui server PHP, bukan membuka file langsung |
Pesan umum “data belum tersedia” bukan bukti pasti bahwa tabel hilang; query bisa gagal karena sebab lain. Periksa penyebab sebelum mengubah skema.
Lampiran A — Tanya jawab UTS
Gunakan setelah memahami langkah pembuatan. Coba jawab tanpa membaca terlebih dahulu.
Apa fungsi aplikasi ini?
Mencatat anggota pelanggan dan pesanan ayam geprek. Data transaksi disimpan melalui PHP ke PostgreSQL.
Apa beda HTML, CSS, JavaScript, dan PHP?
HTML menyusun isi, CSS mengatur tampilan, JavaScript menangani interaksi di browser, PHP memproses permintaan di server.
Mengapa ada validasi JavaScript dan PHP?
JavaScript memberi tanggapan cepat. PHP tetap memvalidasi karena permintaan bisa dikirim tanpa JavaScript.
Apa beda id dan name pada input?
id menandai elemen halaman. name menjadi nama nilai yang dikirim oleh form.
Apa beda GET dan POST di proyek ini?
GET dipakai untuk memilih data edit melalui URL. POST dipakai untuk mengirim perubahan data.
Bagaimana tombol Edit tahu baris yang dipilih?
Link membawa ID. PHP mengambil atau memilih data yang cocok, lalu mengisi form. ID ikut dikirim saat menyimpan.
Mengapa harga tidak dipercaya dari browser?
Nilai browser bisa diubah. PHP mengambil harga dari array menu dan menyimpan harga itu.
Apa primary key dan foreign key?
Primary key mengidentifikasi baris secara unik. Foreign key menghubungkan baris ke data pada tabel lain.
Mengapa memakai prepared statement?
Untuk memasukkan nilai melalui placeholder, sehingga input tidak digabung langsung sebagai struktur SQL.
Apakah menu berada di database?
Belum. Menu berada di array pada includes/menu.php. Tabel database menyimpan anggota dan pesanan.
Apa beda padding dan margin?
Padding adalah jarak di dalam kotak; margin jarak di luar kotak.
Bagaimana membuat tampilan cocok untuk HP?
Memakai meta viewport, media query, dan pembungkus tabel yang dapat digeser.
Apa fungsi session?
Di Geprek Kita, session membawa pesan sementara antarpemintaan. Database menyimpan data utama.
Apakah aplikasi sudah siap menjadi toko online penuh?
Belum. Ini latihan pencatatan, belum ada login, otorisasi, proteksi CSRF, dan pembayaran. Prepared statement saja tidak mencakup semua kebutuhan keamanan aplikasi.
Lampiran B — Latihan dan presentasi
Gunakan salinan latihan jika akan mengubah kode saat belajar.
- Ubah tulisan “Geprek Kita” di header dan jelaskan dampaknya pada semua halaman yang include header.
- Besarkan tombol melalui padding di style.css.
- Ganti background nav a, lalu jelaskan bedanya dengan warna tulisan.
- Ubah harga Original di menu.php, buka ulang halaman, lalu buat pesanan latihan baru.
- Tambah satu item array menu dengan kode unik; periksa daftar menu dan pilihan pesanan.
- Ubah batas jumlah menjadi 50. Sebutkan semua lapisan yang harus diselaraskan: atribut HTML, validasi PHP, dan constraint database. Mengedit file SQL saja tidak otomatis mengubah tabel yang sudah ada.
- Jelaskan query JOIN tanpa membaca handbook.
Jika menambah menu, nama dan kode harus konsisten. Hindari mengganti atau menghapus kode menu yang sudah dipakai data pesanan tanpa merencanakan penanganan data lama.
Presentasi singkat
“Saya membuat Geprek Kita untuk mencatat pelanggan dan pesanan ayam geprek. HTML digunakan untuk form dan tabel, CSS untuk tampilan, serta JavaScript untuk validasi, pencarian, konfirmasi hapus, dan perkiraan total. PHP menerima form, memeriksa input, lalu menjalankan query melalui PDO. Database memiliki tabel anggota dan pesanan yang terhubung lewat anggota_id. Harga diambil dari array PHP supaya perhitungan tidak hanya mengandalkan browser. Saya menempatkan aplikasi ini sebagai Praktikum 8 dan membukanya dari masterpage.”
Setelah itu, tunjukkan satu alur lengkap: tambah anggota → pesan dua porsi → simpan → edit → hapus. Utamakan bisa menjelaskan alasan setiap langkah.
Urutan membaca berkas
- prak8/index.php dan includes/menu.php: mulai dari halaman paling sederhana.
- includes/header.php dan footer.php: pahami bagian halaman yang dipakai ulang.
- assets/css/style.css: ubah satu properti sambil melihat hasil.
- assets/js/app.js: pelajari empat interaksi satu per satu.
- anggota/tambah.php dan proses_tambah.php: ikuti alur form ke SQL.
- includes/env.php dan koneksi.php: pahami konfigurasi tanpa menampilkan rahasia.
- sql/02_geprek.sql: jelaskan hubungan kedua tabel.
- pesanan/list.php: ikuti percabangan tambah, edit, hapus, dan penampilan data.
Handbook ini khusus Geprek Kita. Desain masterpage dan materi lengkap Praktikum 1–6 tidak dibahas sebagai fitur aplikasi Geprek Kita.
Bab 10 — Pengerasan keamanan web dasar (Jobsheet 11)
File utama: prak11/includes/helpers.php, prak11/includes/csrf.php, prak11/includes/session.php, prak11/auth/proses_login.php.
Tujuan: menutup kerentanan keamanan dasar web pada seluruh formulir dan aksi aplikasi.
Langkah kerja
- Buat helper
esc($value)dane($value)menggunakanhtmlspecialchars($value, ENT_QUOTES, 'UTF-8'). - Bungkus seluruh output data kiriman pengguna dan database dengan fungsi escape.
- Buat modul CSRF pada
includes/csrf.phpdengancsrf_token(),csrf_field(), dancsrf_verify(). - Pasang
csrf_field()pada semua formulir POST: login, register, tambah/edit/hapus anggota, dan pesanan. - Jalankan
csrf_verify()pada seluruh berkas pemrosesan sebelum kueri SQL dijalankan. - Regenerasi ID sesi dengan
session_regenerate_id(true)sesaat setelah verifikasi password berhasil. - Audit seluruh kueri database untuk menjamin pemakaian 100% prepared statements PDO.
| Kerentanan | Ancaman | Solusi pada Geprek Kita |
|---|---|---|
| Cross-Site Scripting (XSS) | Injeksi skrip berbahaya pada tampilan browser | Fungsi esc() mengubah karakter khusus menjadi entitas HTML |
| Cross-Site Request Forgery (CSRF) | Pemalsuan kiriman formulir dari situs lain | Token acak 32-byte rahasia per-sesi diverifikasi via hash_equals() |
| SQL Injection | Manipulasi kueri basis data lewat input | Parameter binding pada prepared statements PDO |
| Session Fixation | Pembajakan ID sesi pengguna sebelum login | session_regenerate_id(true) menerbitkan ID baru saat autentikasi |
| Insecure Cookies | Akses cookie sesi oleh skrip pihak ketiga | Cookie flag httponly=true, samesite=Lax, dan secure adaptif |
function csrf_verify(): void {
$token = $_POST['csrf_token'] ?? '';
$sessionToken = $_SESSION['csrf_token'] ?? '';
if ($token === '' || !hash_equals($sessionToken, $token)) {
http_response_code(403);
exit('Permintaan ditolak: token CSRF tidak valid atau telah kedaluwarsa.');
}
}
Urutan pengamanan pada berkas aksi:
auth.php: memastikan pengguna sudah login (tamu dialihkan ke login).csrf_verify(): memastikan formulir dikirim dari aplikasi resmi.- Validasi & sanitasi input: memeriksa tipe data dan batasan nilai.
- Eksekusi query database via prepared statement.
Bab 11 — Integrasi modul transaksi dan stok (Jobsheet 12)
File utama: sql/04_transaksi.sql, prak12/includes/menu.php, prak12/pesanan/list.php, prak12/pesanan/riwayat.php, prak12/index.php.
Tujuan: menghubungkan seluruh entitas aplikasi ke dalam transaksi atomik (ACID) dan mengelola stok fisik menu.
Langkah kerja
- Jalankan
sql/04_transaksi.sqluntuk membuat tabelmenudengan kolomstokdan menambah kolomstatus,user_id, danselesai_padapada tabelpesanan. - Hubungkan
includes/menu.phpke tabelmenuagar sisa stok terbaca real-time. - Terapkan transaksi atomik
$pdo->beginTransaction()saat mencatat pesanan baru. - Kunci baris menu menggunakan
SELECT ... FOR UPDATEuntuk mencegah race condition. - Kurangi stok menu saat pesanan dibuat dan pulihkan stok bila pesanan dibatalkan.
- Tambahkan aksi penyelesaian transaksi (
status = 'selesai') saat pesanan disajikan dan dibayar. - Bangun halaman riwayat transaksi (
pesanan/riwayat.php) menggunakanJOINmulti-tabel (pesanan,anggota,users). - Perbarui Beranda (
index.php) dengan kartu metrik agregat langsung dari database (COUNTdanSUM).
SELECT p.*, a.nama AS nama_anggota, a.no_anggota,
COALESCE(u.nama, 'Petugas Kasir') AS nama_petugas
FROM pesanan p
JOIN anggota a ON a.id = p.anggota_id
LEFT JOIN users u ON u.id = p.user_id
ORDER BY p.id DESC;
Kasir mencatat pesanan
↓
$pdo->beginTransaction()
↓
SELECT stok FROM menu WHERE kode = ? FOR UPDATE (Kunci baris stok)
↓
Cek: stok >= jumlah ? ──[Tidak]──> rollBack() & tampilkan pesan error
↓ [Ya]
UPDATE menu SET stok = stok - jumlah WHERE kode = ?
↓
INSERT INTO pesanan (..., status='menunggu', user_id=...)
↓
$pdo->commit() (Transaksi selesai & stok terpotong)
Aturan bisnis mandiri: pelanggan yang memiliki 3 antrean pesanan berstatus menunggu dibatasi dari menambah pesanan baru sampai pesanan sebelumnya diselesaikan oleh kasir.
Bab 12 — Deployment dan dokumentasi final (Jobsheet 13)
File utama: prak13/includes/config.php, prak13/includes/koneksi.php, prak13/docs/manual-pengguna.md, prak13/docs/naskah-presentasi.md, prak13/README.md.
Tujuan: memisahkan konfigurasi sensitif untuk server produksi, menyusun panduan pengguna, dan menyiapkan naskah demo UAS.
Langkah kerja
- Buat
includes/config.phpuntuk memusatkan pembacaan variabel lingkungan (DATABASE_URL,DB_HOST,DB_USER,DB_PASS,DB_NAME). - Pisahkan inisialisasi koneksi pada
includes/koneksi.phpagar membaca konfigurasi terpusat. - Pastikan file kredensial lokal
.envtidak terunggah ke Git dengan memeriksa.gitignore. - Siapkan berkas serverless
api/prak13.phpdan perbaruivercel.jsonsertarouter.php. - Susun manual operasional pengguna akhir pada
docs/manual-pengguna.md. - Siapkan draf naskah presentasi berdurasi 5–7 menit pada
docs/naskah-presentasi.md. - Perbarui masterpage
index.htmldanREADME.mdutama.
| Berkas Dokumentasi | Peruntukan |
|---|---|
docs/manual-pengguna.md | Petunjuk operasional bagi petugas kasir dan admin warung |
docs/naskah-presentasi.md | Naskah presentasi 5-7 menit untuk demo di hadapan dosen penguji UAS |
docs/security-checklist.md | Laporan audit pengerasan keamanan web dasar OWASP |
prak13/README.md | Dokumentasi arsitektur teknis final, skema ERD, dan panduan instalasi |
docs/laporan/Jobsheet*.md | Laporan resmi praktikum per-jobsheet sesuai format akademik |
Lampiran C — Tanya jawab dan persiapan UAS
Apa beda transaksi biasa dengan transaksi ACID di Jobsheet 12?
Transaksi biasa menjalankan query satu per satu. Jika penyimpanan pesanan berhasil namun pemotongan stok gagal, data menjadi tidak konsisten. Transaksi ACID (beginTransaction, commit, rollBack) menjamin bahwa penyimpanan pesanan dan pemotongan stok terjadi bersamaan atau dibatalkan seluruhnya jika salah satu gagal.
Mengapa perlu SELECT ... FOR UPDATE?
Untuk mengunci baris stok menu selama transaksi berlangsung. Hal ini mencegah dua permintaan bersamaan membaca stok yang sama dan menghasilkan sisa stok minus (*race condition*).
Bagaimana aplikasi mencegah serangan CSRF?
Setiap formulir POST menyertakan token acak rahasia 32-byte yang tersimpan di sesi pengguna. Server memvalidasi token tersebut dengan hash_equals(). Permintaan dari situs eksternal yang tidak memiliki token sesi akan ditolak dengan HTTP 403 Forbidden.
Mengapa fungsi esc() menggunakan ENT_QUOTES?
Flag ENT_QUOTES mengonversi tanda petik ganda (") dan petik tunggal (') menjadi entitas HTML, sehingga nilai kiriman pengguna aman saat disematkan ke dalam atribut HTML (seperti value="...").
Mengapa kredensial basis data dipisahkan ke includes/config.php?
Agar kode aplikasi tidak menyimpan password secara terbuka (*hardcoded*). Di server produksi (seperti Vercel), kredensial dibaca langsung dari Environment Variables, sementara di server lokal dibaca dari file .env yang diabaikan oleh Git.