#CRUD Lengkap
CRUD adalah singkatan dari Create, Read, Update, Delete β empat operasi dasar yang hampir selalu ada di setiap aplikasi web. Di bab ini, kita akan membuat aplikasi CRUD lengkap untuk mengelola data buku tamu.
#Struktur Project
~/Herd/belajar/
βββ config/
β βββ database.php β Koneksi database
βββ templates/
β βββ header.php β Header & navigasi
β βββ footer.php β Footer
βββ buku-tamu/
β βββ index.php β READ β Daftar semua data
β βββ tambah.php β CREATE β Form tambah data
β βββ edit.php β UPDATE β Form edit data
β βββ hapus.php β DELETE β Hapus data
βββ style.css β Styling#Persiapan
#1. Database (kalau belum dibuat)
CREATE DATABASE IF NOT EXISTS belajar_web;
USE belajar_web;
CREATE TABLE IF NOT EXISTS buku_tamu (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
pesan TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);#2. File Koneksi: config/database.php
<?php
$host = 'localhost';
$dbname = 'belajar_web';
$username = 'root';
$password = '';
try {
$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
$username,
$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
die("Koneksi gagal: " . $e->getMessage());
}#3. Template Header: templates/header.php
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $title ?? 'Buku Tamu' ?></title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<nav>
<div class="logo">π Buku Tamu</div>
<ul class="menu">
<li><a href="/buku-tamu/">Daftar Tamu</a></li>
<li><a href="/buku-tamu/tambah.php">Tambah Baru</a></li>
</ul>
</nav>
<div class="container">#4. Template Footer: templates/footer.php
</div>
<footer>
<p>© 2026 Buku Tamu β Belajar PHP CRUD</p>
</footer>
</body>
</html>#5. Styling: style.css
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', sans-serif;
line-height: 1.6;
color: #333;
background: #f0f2f5;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 30px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
.logo { font-size: 20px; font-weight: bold; }
.menu { display: flex; gap: 20px; list-style: none; }
.menu a { color: white; text-decoration: none; font-weight: 500; }
.menu a:hover { text-decoration: underline; }
.container {
max-width: 900px;
margin: 30px auto;
padding: 0 20px;
}
h1 {
margin-bottom: 20px;
color: #333;
}
/* Tabel */
table {
width: 100%;
border-collapse: collapse;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
th { background: #667eea; color: white; padding: 12px 15px; text-align: left; }
td { padding: 12px 15px; border-bottom: 1px solid #eee; }
tr:last-child td { border-bottom: none; }
tr:hover { background: #f8f9fe; }
/* Tombol */
.btn {
display: inline-block;
padding: 6px 14px;
border-radius: 6px;
text-decoration: none;
font-size: 14px;
font-weight: 500;
border: none;
cursor: pointer;
}
.btn-primary { background: #667eea; color: white; }
.btn-primary:hover { background: #5a6fd6; }
.btn-warning { background: #ffc107; color: #333; }
.btn-warning:hover { background: #e0a800; }
.btn-danger { background: #dc3545; color: white; }
.btn-danger:hover { background: #c82333; }
.btn-success { background: #28a745; color: white; }
.btn-success:hover { background: #218838; }
/* Form */
.form-card {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
max-width: 500px;
}
.form-group { margin-bottom: 18px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 600; color: #444; }
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px 14px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 15px;
font-family: inherit;
}
.form-group input:focus,
.form-group textarea:focus { outline: none; border-color: #667eea; }
.btn-submit {
width: 100%;
padding: 12px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
}
.btn-submit:hover { background: #5a6fd6; }
/* Alert */
.alert {
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 14px;
}
.alert-success { background: #e8f5e9; border: 1px solid #28a745; color: #28a745; }
.alert-error { background: #fde8e8; border: 1px solid #dc3545; color: #dc3545; }
/* Actions */
.actions { display: flex; gap: 6px; }
footer {
text-align: center;
padding: 20px;
background: #333;
color: #aaa;
margin-top: 30px;
}#CREATE β Tambah Data
File buku-tamu/tambah.php:
<?php
require __DIR__ . '/../config/database.php';
$title = 'Tambah Tamu Baru';
$nama = '';
$email = '';
$pesan = '';
$errors = [];
$sukses = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Ambil dan sanitasi data
$nama = htmlspecialchars(trim($_POST['nama'] ?? ''));
$email = filter_var(trim($_POST['email'] ?? ''), FILTER_SANITIZE_EMAIL);
$pesan = htmlspecialchars(trim($_POST['pesan'] ?? ''));
// Validasi
if (empty($nama)) $errors[] = "Nama wajib diisi";
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = "Email tidak valid";
if (empty($pesan)) $errors[] = "Pesan wajib diisi";
// Simpan ke database
if (empty($errors)) {
$stmt = $pdo->prepare("INSERT INTO buku_tamu (nama, email, pesan) VALUES (?, ?, ?)");
$stmt->execute([$nama, $email, $pesan]);
$sukses = true;
// Reset form
$nama = '';
$email = '';
$pesan = '';
}
}
require __DIR__ . '/../templates/header.php';
?>
<h1>β Tambah Tamu Baru</h1>
<?php if ($sukses): ?>
<div class="alert alert-success">
β
Data berhasil ditambahkan!
<a href="/buku-tamu/">Lihat daftar β</a>
</div>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<div class="alert alert-error">
<?php foreach ($errors as $error): ?>
<p>β οΈ <?= $error ?></p>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="form-card">
<form method="POST">
<div class="form-group">
<label for="nama">Nama</label>
<input type="text" id="nama" name="nama" value="<?= $nama ?>" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" value="<?= $email ?>" required>
</div>
<div class="form-group">
<label for="pesan">Pesan</label>
<textarea id="pesan" name="pesan" rows="5" required><?= $pesan ?></textarea>
</div>
<button type="submit" class="btn-submit">πΎ Simpan</button>
</form>
</div>
<?php require __DIR__ . '/../templates/footer.php'; ?>#READ β Tampilkan Data
File buku-tamu/index.php:
<?php
require __DIR__ . '/../config/database.php';
$title = 'Daftar Buku Tamu';
// Pesan sukses dari redirect
$pesan_sukses = $_GET['sukses'] ?? '';
// Ambil semua data, terbaru di atas
$stmt = $pdo->query("SELECT * FROM buku_tamu ORDER BY created_at DESC");
$bukuTamu = $stmt->fetchAll();
require __DIR__ . '/../templates/header.php';
?>
<h1>π Daftar Buku Tamu</h1>
<?php if ($pesan_sukses === 'hapus'): ?>
<div class="alert alert-success">β
Data berhasil dihapus!</div>
<?php elseif ($pesan_sukses === 'edit'): ?>
<div class="alert alert-success">β
Data berhasil diupdate!</div>
<?php endif; ?>
<p style="margin-bottom: 15px;">
<a href="tambah.php" class="btn btn-primary">β Tambah Tamu Baru</a>
<span style="color: #999; margin-left: 10px;">Total: <?= count($bukuTamu) ?> data</span>
</p>
<table>
<thead>
<tr>
<th>No</th>
<th>Nama</th>
<th>Pesan</th>
<th>Waktu</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<?php if (empty($bukuTamu)): ?>
<tr>
<td colspan="5" style="text-align: center; padding: 30px; color: #999;">
Belum ada data. <a href="tambah.php">Tambah data pertama β</a>
</td>
</tr>
<?php else: ?>
<?php foreach ($bukuTamu as $i => $row): ?>
<tr>
<td><?= $i + 1 ?></td>
<td>
<strong><?= htmlspecialchars($row['nama']) ?></strong><br>
<small style="color:#999"><?= htmlspecialchars($row['email']) ?></small>
</td>
<td><?= htmlspecialchars($row['pesan']) ?></td>
<td>
<small><?= date('d M Y', strtotime($row['created_at'])) ?></small><br>
<small style="color:#999"><?= date('H:i', strtotime($row['created_at'])) ?></small>
</td>
<td>
<div class="actions">
<a href="edit.php?id=<?= $row['id'] ?>" class="btn btn-warning">βοΈ Edit</a>
<a href="hapus.php?id=<?= $row['id'] ?>" class="btn btn-danger"
onclick="return confirm('Yakin ingin menghapus data ini?')">ποΈ Hapus</a>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php require __DIR__ . '/../templates/footer.php'; ?>#UPDATE β Edit Data
File buku-tamu/edit.php:
<?php
require __DIR__ . '/../config/database.php';
$title = 'Edit Data Tamu';
$errors = [];
// Ambil ID dari URL
$id = (int) ($_GET['id'] ?? 0);
// Ambil data yang akan diedit
$stmt = $pdo->prepare("SELECT * FROM buku_tamu WHERE id = ?");
$stmt->execute([$id]);
$data = $stmt->fetch();
// Kalau data tidak ditemukan, redirect
if (!$data) {
header("Location: /buku-tamu/");
exit;
}
$nama = $data['nama'];
$email = $data['email'];
$pesan = $data['pesan'];
// Proses form update
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nama = htmlspecialchars(trim($_POST['nama'] ?? ''));
$email = filter_var(trim($_POST['email'] ?? ''), FILTER_SANITIZE_EMAIL);
$pesan = htmlspecialchars(trim($_POST['pesan'] ?? ''));
// Validasi
if (empty($nama)) $errors[] = "Nama wajib diisi";
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = "Email tidak valid";
if (empty($pesan)) $errors[] = "Pesan wajib diisi";
// Update database
if (empty($errors)) {
$stmt = $pdo->prepare("UPDATE buku_tamu SET nama = ?, email = ?, pesan = ? WHERE id = ?");
$stmt->execute([$nama, $email, $pesan, $id]);
header("Location: /buku-tamu/?sukses=edit");
exit;
}
}
require __DIR__ . '/../templates/header.php';
?>
<h1>βοΈ Edit Data Tamu</h1>
<?php if (!empty($errors)): ?>
<div class="alert alert-error">
<?php foreach ($errors as $error): ?>
<p>β οΈ <?= $error ?></p>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="form-card">
<form method="POST">
<div class="form-group">
<label for="nama">Nama</label>
<input type="text" id="nama" name="nama" value="<?= $nama ?>" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" value="<?= $email ?>" required>
</div>
<div class="form-group">
<label for="pesan">Pesan</label>
<textarea id="pesan" name="pesan" rows="5" required><?= $pesan ?></textarea>
</div>
<button type="submit" class="btn-submit">πΎ Update</button>
</form>
<p style="margin-top: 15px;">
<a href="/buku-tamu/">β Kembali ke daftar</a>
</p>
</div>
<?php require __DIR__ . '/../templates/footer.php'; ?>#DELETE β Hapus Data
File buku-tamu/hapus.php:
<?php
require __DIR__ . '/../config/database.php';
// Ambil ID dari URL
$id = (int) ($_GET['id'] ?? 0);
if ($id > 0) {
// Cek apakah data ada
$stmt = $pdo->prepare("SELECT id FROM buku_tamu WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->fetch()) {
// Hapus data
$stmt = $pdo->prepare("DELETE FROM buku_tamu WHERE id = ?");
$stmt->execute([$id]);
}
}
// Redirect kembali ke daftar
header("Location: /buku-tamu/?sukses=hapus");
exit;#Alur Kerja CRUD
βββββββββββββββ ββββββββββββββββββ
β index.php ββββββΆβ tambah.php β
β (READ) β β (CREATE) β
β β ββββββββββββββββββ
β Daftar β
β semua β ββββββββββββββββββ
β data ββββββΆβ edit.php?id= β
β β β (UPDATE) β
β β ββββββββββββββββββ
β β
β β ββββββββββββββββββ
β ββββββΆβ hapus.php?id= β
β β β (DELETE) β
βββββββββββββββ ββββββββββββββββββ#Tips CRUD di Dunia Nyata
- Selalu gunakan prepared statement β mencegah SQL Injection
- Validasi di server β meskipun sudah ada validasi JS di browser
- Sanitasi output β gunakan
htmlspecialchars()saat menampilkan data - Redirect setelah POST β cegah duplikasi data saat refresh (PRG pattern)
- Konfirmasi sebelum hapus β pakai
confirm()JavaScript - Soft delete β di production, data biasanya tidak benar-benar dihapus, hanya ditandai
// Setelah INSERT/UPDATE berhasil:
header("Location: /buku-tamu/?sukses=edit");
exit; // Wajib! Hentikan eksekusi setelah redirectKenapa? Kalau tidak redirect, pengguna yang me-refresh halaman akan mengirim form lagi (duplikasi data).
#Selanjutnya
CRUD sudah lengkap! Untuk latihan project berikutnya, kamu bisa membuat Dashboard Nilai Siswa dengan PHP dan MySQL. Tapi sebelum aplikasi dipakai banyak orang, ingat bahwa siapapun masih bisa mengakses halaman edit dan hapus. Kita perlu autentikasi β sistem login agar hanya pengguna tertentu yang bisa mengelola data. Lanjut ke Session & Autentikasi β.