#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
Best Practices
- 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 :::
:::warning PRG Pattern (Post-Redirect-Get) Setelah berhasil menyimpan/mengupdate data, selalu redirect ke halaman lain:
// 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! Tapi siapapun bisa mengakses halaman edit dan hapus. Kita perlu autentikasi β sistem login agar hanya pengguna tertentu yang bisa mengelola data. Lanjut ke Session & Autentikasi β.