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">
    </div>
    <footer>
        <p>&copy; 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

TIP
  1. Selalu gunakan prepared statement β€” mencegah SQL Injection
  2. Validasi di server β€” meskipun sudah ada validasi JS di browser
  3. Sanitasi output β€” gunakan htmlspecialchars() saat menampilkan data
  4. Redirect setelah POST β€” cegah duplikasi data saat refresh (PRG pattern)
  5. Konfirmasi sebelum hapus β€” pakai confirm() JavaScript
  6. Soft delete β€” di production, data biasanya tidak benar-benar dihapus, hanya ditandai
WARNING
// Setelah INSERT/UPDATE berhasil:
header("Location: /buku-tamu/?sukses=edit");
exit;  // Wajib! Hentikan eksekusi setelah redirect

Kenapa? 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 β†’.