Project PHP MySQL CRUD Artikel (Blog Sederhana) – Panduan Lengkap Membuat Sistem Manajemen Konten
Membangun sistem manajemen artikel/blog sederhana adalah proyek sempurna untuk mempelajari operasi database dasar. Dalam panduan komprehensif ini, kita akan membuat aplikasi PHP MySQL yang mencakup semua fungsi CRUD:
✅ Create – Formulir tambah artikel baru
✅ Read – Menampilkan daftar artikel
✅ Update – Formulir edit artikel
✅ Delete – Fungsi hapus artikel
✅ Antarmuka Responsif menggunakan Bootstrap 5 CDN
✅ Proteksi Keamanan dasar
Persiapan Proyek
Struktur Folder yang Direkomendasikan
blog_sederhana/ ├── config/ │ └── database.php ├── includes/ │ ├── header.php │ ├── footer.php │ └── functions.php ├── articles/ │ ├── index.php │ ├── create.php │ ├── edit.php │ └── delete.php └── assets/ └── css/ └── style.css
Buat Database MySQL
CREATE DATABASE blog_sederhana; USE blog_sederhana; CREATE TABLE articles ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
Langkah 1: Konfigurasi Database (config/database.php)
<?php
// Konfigurasi Database
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'blog_sederhana');
// Koneksi dengan PDO untuk keamanan lebih baik
try {
$dsn = "mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) {
die("Koneksi database gagal: " . $e->getMessage());
}
?>
Langkah 2: Implemtasi includes code
Header (includes/header.php)
<!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sistem Artikel - <?= $pageTitle ?? 'Blog Sederhana azroi' ?></title> <!-- Bootstrap 5 CSS dari CDN --> <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet"> <style> .article-content { white-space: pre-line; } .pagination { justify-content: center; } </style> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4"> <div class="container"> <a class="navbar-brand" href="/articles">Blog Sederhana</a> <div class="collapse navbar-collapse"> <ul class="navbar-nav me-auto"> <li class="nav-item"> <a class="nav-link" href="/articles">Artikel</a> </li> <li class="nav-item"> <a class="nav-link" href="/articles/create.php">Tambah Artikel</a> </li> </ul> </div> </div> </nav> <div class="container">
Footer (includes/footer.php)
</div> <footer class="mt-5 py-3 bg-light"> <div class="container text-center"> <p>© <?= date('Y') ?> Blog Sederhana - Azroi.</p> </div> </footer> <!-- Bootstrap 5 JS dari CDN --> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script> </body> </html>
Langkah 3: Halaman Utama Artikel (articles/index.php)
<?php require_once __DIR__.'/../config/database.php'; session_start(); $pageTitle = "Daftar Artikel"; // Pagination $limit = 5; $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; $offset = ($page - 1) * $limit; // Hitung total artikel $totalArticles = $pdo->query("SELECT COUNT(*) FROM articles")->fetchColumn(); $totalPages = ceil($totalArticles / $limit); // Query dengan pagination $stmt = $pdo->prepare("SELECT * FROM articles ORDER BY created_at DESC LIMIT :limit OFFSET :offset"); $stmt->bindParam(':limit', $limit, PDO::PARAM_INT); $stmt->bindParam(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); $articles = $stmt->fetchAll(); include __DIR__.'/../includes/header.php'; ?> <!-- Notifikasi --> <?php if (isset($_SESSION['success'])): ?> <div class="alert alert-success alert-dismissible fade show"> <?= $_SESSION['success'] ?> <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> <?php unset($_SESSION['success']); ?> <?php endif; ?> <h2 class="mb-4">Daftar Artikel</h2> <a href="create.php" class="btn btn-primary mb-3"> <i class="bi bi-plus-circle"></i> Tambah Artikel </a> <div class="table-responsive"> <table class="table table-striped table-hover"> <thead class="table-dark"> <tr> <th width="5%">#</th> <th width="30%">Judul</th> <th width="20%">Tanggal</th> <th width="15%">Aksi</th> </tr> </thead> <tbody> <?php foreach ($articles as $index => $article): ?> <tr> <td><?= $index + 1 + $offset ?></td> <td><?= htmlspecialchars($article['title']) ?></td> <td><?= date('d/m/Y H:i', strtotime($article['created_at'])) ?></td> <td> <a href="edit.php?id=<?= $article['id'] ?>" class="btn btn-sm btn-warning"> <i class="bi bi-pencil"></i> Edit </a> <form action="delete.php" method="POST" class="d-inline"> <input type="hidden" name="id" value="<?= $article['id'] ?>"> <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Yakin menghapus artikel ini?')"> <i class="bi bi-trash"></i> Hapus </button> </form> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> <!-- Pagination --> <?php if ($totalPages > 1): ?> <nav aria-label="Page navigation"> <ul class="pagination"> <li class="page-item <?= $page <= 1 ? 'disabled' : '' ?>"> <a class="page-link" href="?page=<?= $page-1 ?>">Sebelumnya</a> </li> <?php for ($i = 1; $i <= $totalPages; $i++): ?> <li class="page-item <?= $i == $page ? 'active' : '' ?>"> <a class="page-link" href="?page=<?= $i ?>"><?= $i ?></a> </li> <?php endfor; ?> <li class="page-item <?= $page >= $totalPages ? 'disabled' : '' ?>"> <a class="page-link" href="?page=<?= $page+1 ?>">Selanjutnya</a> </li> </ul> </nav> <?php endif; ?> <?php include __DIR__.'/../includes/footer.php'; ?>
Langkah 4: Membuat Artikel Baru (articles/create.php)
<?php require_once __DIR__.'/../config/database.php'; session_start(); $pageTitle = "Tambah Artikel Baru"; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $title = trim($_POST['title']); $content = trim($_POST['content']); // Validasi $errors = []; if (empty($title)) $errors[] = "Judul artikel wajib diisi"; if (strlen($title) > 255) $errors[] = "Judul maksimal 255 karakter"; if (empty($content)) $errors[] = "Konten artikel wajib diisi"; if (empty($errors)) { try { $stmt = $pdo->prepare("INSERT INTO articles (title, content) VALUES (?, ?)"); $stmt->execute([$title, $content]); $_SESSION['success'] = "Artikel berhasil ditambahkan!"; header("Location: index.php"); exit; } catch (PDOException $e) { $errors[] = "Gagal menyimpan artikel: " . $e->getMessage(); } } } include __DIR__.'/../includes/header.php'; ?> <h2 class="mb-4">Tambah Artikel Baru</h2> <?php if (!empty($errors)): ?> <div class="alert alert-danger"> <h5>Terjadi Kesalahan:</h5> <ul class="mb-0"> <?php foreach ($errors as $error): ?> <li><?= $error ?></li> <?php endforeach; ?> </ul> </div> <?php endif; ?> <form method="POST"> <div class="mb-3"> <label for="title" class="form-label">Judul Artikel</label> <input type="text" class="form-control" id="title" name="title" value="<?= htmlspecialchars($_POST['title'] ?? '') ?>" required> </div> <div class="mb-3"> <label for="content" class="form-label">Konten Artikel</label> <textarea class="form-control" id="content" name="content" rows="10" required><?= htmlspecialchars($_POST['content'] ?? '') ?></textarea> </div> <div class="d-grid gap-2 d-md-flex justify-content-md-end"> <a href="index.php" class="btn btn-secondary me-md-2">Kembali</a> <button type="submit" class="btn btn-primary">Simpan Artikel</button> </div> </form> <?php include __DIR__.'/../includes/footer.php'; ?>
Langkah 5: Mengedit Artikel (articles/edit.php)
<?php require_once __DIR__.'/../config/database.php'; session_start(); $pageTitle = "Edit Artikel"; // Ambil ID dari URL $id = isset($_GET['id']) ? (int)$_GET['id'] : 0; // Ambil data artikel $stmt = $pdo->prepare("SELECT * FROM articles WHERE id = ?"); $stmt->execute([$id]); $article = $stmt->fetch(); if (!$article) { $_SESSION['error'] = "Artikel tidak ditemukan"; header("Location: index.php"); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $title = trim($_POST['title']); $content = trim($_POST['content']); $errors = []; if (empty($title)) $errors[] = "Judul wajib diisi"; if (strlen($title) > 255) $errors[] = "Judul maksimal 255 karakter"; if (empty($content)) $errors[] = "Konten wajib diisi"; if (empty($errors)) { try { $stmt = $pdo->prepare("UPDATE articles SET title = ?, content = ? WHERE id = ?"); $stmt->execute([$title, $content, $id]); $_SESSION['success'] = "Artikel berhasil diperbarui!"; header("Location: index.php"); exit; } catch (PDOException $e) { $errors[] = "Gagal update artikel: " . $e->getMessage(); } } } include __DIR__.'/../includes/header.php'; ?> <h2 class="mb-4">Edit Artikel</h2> <?php if (!empty($errors)): ?> <div class="alert alert-danger"> <h5>Terjadi Kesalahan:</h5> <ul class="mb-0"> <?php foreach ($errors as $error): ?> <li><?= $error ?></li> <?php endforeach; ?> </ul> </div> <?php endif; ?> <form method="POST"> <div class="mb-3"> <label for="title" class="form-label">Judul Artikel</label> <input type="text" class="form-control" id="title" name="title" value="<?= htmlspecialchars($article['title']) ?>" required> </div> <div class="mb-3"> <label for="content" class="form-label">Konten Artikel</label> <textarea class="form-control" id="content" name="content" rows="10" required><?= htmlspecialchars($article['content']) ?></textarea> </div> <div class="d-grid gap-2 d-md-flex justify-content-md-end"> <a href="index.php" class="btn btn-secondary me-md-2">Batal</a> <button type="submit" class="btn btn-primary">Update Artikel</button> </div> </form> <?php include __DIR__.'/../includes/footer.php'; ?>
Langkah 6: Menghapus Artikel (articles/delete.php)
<?php
require_once __DIR__.'/../config/database.php';
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
if ($id > 0) {
try {
// Cek apakah artikel ada
$stmt = $pdo->prepare("SELECT id FROM articles WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->fetch()) {
// Hapus artikel
$deleteStmt = $pdo->prepare("DELETE FROM articles WHERE id = ?");
if ($deleteStmt->execute([$id])) {
$_SESSION['success'] = "Artikel berhasil dihapus";
} else {
$_SESSION['error'] = "Gagal menghapus artikel";
}
} else {
$_SESSION['error'] = "Artikel tidak ditemukan";
}
} catch (PDOException $e) {
$_SESSION['error'] = "Error: " . $e->getMessage();
}
} else {
$_SESSION['error'] = "ID artikel tidak valid";
}
}
header("Location: index.php");
exit;
?>
Kesimpulan dan Pengembangan Lanjutan
Yang Sudah Dibangun:
✔ Sistem CRUD artikel lengkap
✔ Tampilan responsif dengan Bootstrap 5
✔ Pagination untuk navigasi artikel
✔ Validasi input dan proteksi keamanan
✔ Notifikasi operasi berhasil/gagal