Project PHP MySQL: Membuat Aplikasi CRUD dengan Upload & Preview Gambar Menggunakan Bootstrap 5
PHP dan MySQL adalah kombinasi powerful untuk membangun aplikasi web dinamis. Dalam tutorial ini, kita akan membuat aplikasi CRUD (Create, Read, Update, Delete) dengan fitur upload dan preview gambar menggunakan Bootstrap 5 untuk tampilan yang modern dan responsif. Project ini sangat cocok untuk pemula yang ingin mempelajari pengembangan web full-stack.
Fitur Utama Aplikasi
✅ Upload Gambar – Menyimpan gambar ke server & database
✅ Preview Gambar – Menampilkan daftar gambar dalam bentuk grid
✅ Operasi CRUD Lengkap
✅ Tampilan Modern dengan Bootstrap 5
✅ Validasi File (hanya menerima JPG, JPEG, PNG, GIF)
Persiapan Project
1. Struktur Folder
php-crud-upload/ ├── assets/ │ ├── css/ │ │ └── style.css │ └── images/ ├── config/ │ └── database.php ├── uploads/ ├── create.php ├── read.php ├── update.php ├── delete.php └── index.php
2. Setup Database
Buat database crud_upload
dan tabel images
:
CREATE DATABASE crud_upload; USE crud_upload; CREATE TABLE images ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, filename VARCHAR(255) NOT NULL, uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
Implementasi Kode
1. Konfigurasi Database (config/database.php)
<?php
$host = "localhost";
$user = "root";
$pass = "";
$dbname = "crud_upload";
try {
$conn = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
die("Koneksi gagal: " . $e->getMessage());
}
?>
2. Tampilan Utama (index.php)
<?php include 'config/database.php'; $stmt = $conn->query("SELECT * FROM images ORDER BY uploaded_at DESC"); $images = $stmt->fetchAll(); ?> <!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CRUD Upload Gambar</title> <!-- Bootstrap 5 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Font Awesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> .card-img-top { height: 200px; object-fit: cover; } </style> </head> <body class="bg-light"> <div class="container py-5"> <div class="row"> <div class="col-md-8 mx-auto"> <div class="card shadow"> <div class="card-header bg-primary text-white"> <h3 class="text-center"> <i class="fas fa-image me-2"></i>CRUD Upload Gambar </h3> </div> <!-- Form Upload --> <div class="card-body"> <form action="create.php" method="post" enctype="multipart/form-data" class="mb-4"> <div class="row g-3"> <div class="col-md-6"> <input type="text" name="title" class="form-control" placeholder="Judul Gambar" required> </div> <div class="col-md-4"> <input type="file" name="image" class="form-control" accept="image/*" required> </div> <div class="col-md-2"> <button type="submit" name="upload" class="btn btn-primary w-100"> <i class="fas fa-upload me-1"></i> Upload </button> </div> </div> </form> <!-- Daftar Gambar --> <div class="row row-cols-1 row-cols-md-3 g-4"> <?php foreach($images as $image): ?> <div class="col"> <div class="card h-100 shadow-sm"> <img src="uploads/<?= $image['filename'] ?>" class="card-img-top" alt="<?= $image['title'] ?>"> <div class="card-body"> <h5 class="card-title"><?= $image['title'] ?></h5> <small class="text-muted"> <?= date('d M Y H:i', strtotime($image['uploaded_at'])) ?> </small> </div> <div class="card-footer bg-white"> <a href="update.php?id=<?= $image['id'] ?>" class="btn btn-sm btn-warning"> <i class="fas fa-edit"></i> </a> <a href="delete.php?id=<?= $image['id'] ?>" class="btn btn-sm btn-danger" onclick="return confirm('Hapus gambar ini?')"> <i class="fas fa-trash"></i> </a> </div> </div> </div> <?php endforeach; ?> </div> </div> </div> </div> </div> </div> <!-- Bootstrap 5 JS --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> </html>
3. Proses Upload (create.php)
<?php
include 'config/database.php';
if(isset($_POST['upload'])) {
$title = $_POST['title'];
$targetDir = "uploads/";
$fileName = uniqid() . '_' . basename($_FILES["image"]["name"]);
$targetFilePath = $targetDir . $fileName;
$fileType = strtolower(pathinfo($targetFilePath, PATHINFO_EXTENSION));
// Validasi
$allowedTypes = ["jpg", "jpeg", "png", "gif"];
if(in_array($fileType, $allowedTypes)) {
if(move_uploaded_file($_FILES["image"]["tmp_name"], $targetFilePath)) {
$stmt = $conn->prepare("INSERT INTO images (title, filename) VALUES (?, ?)");
$stmt->execute([$title, $fileName]);
$_SESSION['success'] = "Gambar berhasil diupload!";
} else {
$_SESSION['error'] = "Gagal mengupload gambar.";
}
} else {
$_SESSION['error'] = "Hanya format JPG, JPEG, PNG & GIF yang diperbolehkan.";
}
header("Location: index.php");
exit();
}
?>
4. Update Data (update.php)
<?php session_start(); include 'config/database.php'; // Check if ID exists if(!isset($_GET['id'])) { $_SESSION['error'] = "ID gambar tidak valid"; header("Location: index.php"); exit(); } $id = $_GET['id']; try { $stmt = $conn->prepare("SELECT * FROM images WHERE id=?"); $stmt->execute([$id]); $image = $stmt->fetch(); if(!$image) { $_SESSION['error'] = "Gambar tidak ditemukan"; header("Location: index.php"); exit(); } } catch(PDOException $e) { $_SESSION['error'] = "Error: " . $e->getMessage(); header("Location: index.php"); exit(); } if(isset($_POST['update'])) { $title = $_POST['title']; $updateData = [':title' => $title, ':id' => $id]; $success = false; // Handle file upload if new image is provided if(!empty($_FILES["image"]["name"])) { $targetDir = "uploads/"; $fileName = uniqid() . '_' . basename($_FILES["image"]["name"]); $targetFilePath = $targetDir . $fileName; $fileType = strtolower(pathinfo($targetFilePath, PATHINFO_EXTENSION)); // Validate file type $allowedTypes = ["jpg", "jpeg", "png", "gif"]; if(in_array($fileType, $allowedTypes)) { // Upload new file if(move_uploaded_file($_FILES["image"]["tmp_name"], $targetFilePath)) { // Delete old file if(file_exists("uploads/" . $image['filename'])) { unlink("uploads/" . $image['filename']); } $updateData[':filename'] = $fileName; $success = true; } else { $_SESSION['error'] = "Gagal mengupload gambar baru"; } } else { $_SESSION['error'] = "Hanya format JPG, JPEG, PNG & GIF yang diperbolehkan"; } } else { $success = true; } // Update database if validation passed if($success) { try { $sql = "UPDATE images SET title=:title" . (isset($updateData[':filename']) ? ", filename=:filename" : "") . " WHERE id=:id"; $stmt = $conn->prepare($sql); $stmt->execute($updateData); $_SESSION['success'] = "Data berhasil diupdate!"; header("Location: index.php"); exit(); } catch(PDOException $e) { $_SESSION['error'] = "Error database: " . $e->getMessage(); } } } ?> <!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Edit Gambar - Azroi Tech</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> .preview-image { max-height: 300px; object-fit: contain; margin-bottom: 20px; border-radius: 5px; } </style> </head> <body class="bg-light"> <div class="container py-5"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card shadow"> <div class="card-header bg-primary text-white"> <h3 class="text-center"> <i class="fas fa-edit me-2"></i>Edit Gambar </h3> </div> <div class="card-body"> <!-- Error/Success Messages --> <?php if(isset($_SESSION['error'])): ?> <div class="alert alert-danger"> <?= $_SESSION['error']; unset($_SESSION['error']); ?> </div> <?php endif; ?> <?php if(isset($_SESSION['success'])): ?> <div class="alert alert-success"> <?= $_SESSION['success']; unset($_SESSION['success']); ?> </div> <?php endif; ?> <!-- Current Image Preview --> <div class="text-center"> <img src="uploads/<?= htmlspecialchars($image['filename']) ?>" alt="<?= htmlspecialchars($image['title']) ?>" class="preview-image img-thumbnail"> <p class="text-muted">Gambar saat ini</p> </div> <!-- Edit Form --> <form action="update.php?id=<?= $id ?>" method="post" enctype="multipart/form-data"> <div class="mb-3"> <label for="title" class="form-label">Judul Gambar</label> <input type="text" class="form-control" id="title" name="title" value="<?= htmlspecialchars($image['title']) ?>" required> </div> <div class="mb-3"> <label for="image" class="form-label">Gambar Baru (Opsional)</label> <input class="form-control" type="file" id="image" name="image" accept="image/jpeg, image/png, image/gif"> <div class="form-text"> Kosongkan jika tidak ingin mengubah gambar. Format: JPG, PNG, GIF (Max 2MB) </div> </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"> <i class="fas fa-arrow-left me-1"></i> Kembali </a> <button type="submit" name="update" class="btn btn-primary"> <i class="fas fa-save me-1"></i> Simpan Perubahan </button> </div> </form> </div> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> </html>
Keunggulan Project Ini
- Responsive Design – Tampilan optimal di semua perangkat
- Modern UI – Menggunakan Bootstrap 5 dan Font Awesome
- Keamanan:
- Nama file unik dengan
uniqid()
- Validasi ekstensi file
- PDO untuk mencegah SQL injection
- Nama file unik dengan
- User Experience:
- Notifikasi sukses/gagal
- Konfirmasi sebelum menghapus
- Preview gambar langsung
Tips Pengembangan:
- Tambahkan pagination untuk data yang banyak
- Implementasi AJAX untuk operasi CRUD
- Tambahkan fitur multi-upload
Dengan menyelesaikan project ini, Anda telah mempelajari:
✔ Konsep CRUD dengan PHP & MySQL
✔ Upload dan manajemen file
✔ Integrasi Bootstrap 5