Membuat CRUD Sederhana PHP Bootstrap: Tutorial Lengkap untuk Pemula
Dalam dunia web development, membuat CRUD sederhana PHP Bootstrap adalah skill fundamental yang harus dikuasai setiap developer. CRUD (Create, Read, Update, Delete) merupakan operasi dasar dalam pengelolaan data yang akan Anda temukan di hampir setiap aplikasi web modern.
Tutorial ini akan memandu Anda step-by-step untuk membangun aplikasi CRUD yang responsive dan user-friendly menggunakan PHP sebagai backend dan Bootstrap sebagai framework CSS. Anda akan belajar mulai dari setup database MySQL hingga implementasi fitur lengkap dengan interface yang menarik.
🎯 Apa yang Akan Anda Pelajari?
- Konsep dasar CRUD dan implementasinya
- Setup database MySQL dan struktur tabel
- Integrasi PHP dengan MySQL menggunakan PDO
- Desain responsive dengan Bootstrap 5
- Validasi form dan error handling
- Security best practices untuk aplikasi web
- Pagination dan search functionality
📋 Persiapan dan Requirements
Sebelum memulai membuat CRUD sederhana PHP Bootstrap, pastikan Anda sudah menyiapkan environment development berikut:
🛠️ Tools yang Dibutuhkan
- XAMPP/WAMP/MAMP: Local server untuk PHP dan MySQL
- Text Editor: VS Code, Sublime Text, atau Notepad++
- Web Browser: Chrome, Firefox, atau browser modern lainnya
- Bootstrap 5: Framework CSS untuk responsive design
📚 Pengetahuan Dasar
- HTML dan CSS fundamental
- PHP basic syntax dan functions
- MySQL database operations
- Bootstrap grid system (optional tapi recommended)
🗄️ Setup Database MySQL
Langkah pertama dalam membuat aplikasi CRUD adalah menyiapkan database. Mari buat database dan tabel untuk menyimpan data mahasiswa:
📊 Struktur Database
-- Buat database baru
CREATE DATABASE crud_mahasiswa;
USE crud_mahasiswa;
-- Buat tabel mahasiswa
CREATE TABLE mahasiswa (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
nim VARCHAR(20) NOT NULL UNIQUE,
nama VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
jurusan VARCHAR(50) NOT NULL,
alamat TEXT,
tanggal_lahir DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insert sample data
INSERT INTO mahasiswa (nim, nama, email, jurusan, alamat, tanggal_lahir) VALUES
('2021001', 'Ahmad Rizki', 'ahmad.rizki@email.com', 'Teknik Informatika', 'Jakarta Selatan', '2000-05-15'),
('2021002', 'Siti Nurhaliza', 'siti.nurhaliza@email.com', 'Sistem Informasi', 'Bandung', '1999-12-20'),
('2021003', 'Budi Santoso', 'budi.santoso@email.com', 'Teknik Komputer', 'Surabaya', '2001-03-10');
🔧 Konfigurasi Koneksi Database
Buat file konfigurasi untuk menghubungkan PHP dengan MySQL menggunakan PDO (PHP Data Objects) yang lebih secure:
📁 File: config/database.php
<?php
class Database {
private $host = "localhost";
private $db_name = "crud_mahasiswa";
private $username = "root";
private $password = "";
public $conn;
public function getConnection() {
$this->conn = null;
try {
$this->conn = new PDO(
"mysql:host=" . $this->host . ";dbname=" . $this->db_name,
$this->username,
$this->password
);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch(PDOException $exception) {
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
?>
🏗️ Struktur Project
Organisasi file yang baik sangat penting untuk maintainability. Berikut struktur folder yang akan kita gunakan:
crud-mahasiswa/
├── config/
│ └── database.php
├── includes/
│ ├── header.php
│ └── footer.php
├── assets/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── script.js
├── index.php
├── create.php
├── edit.php
├── delete.php
└── functions.php
🎨 Template HTML dengan Bootstrap
Mari buat template dasar dengan Bootstrap 5 untuk interface yang responsive dan modern:
📁 File: 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><?php echo isset($page_title) ? $page_title : 'CRUD Mahasiswa'; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="index.php">
<i class="fas fa-graduation-cap me-2"></i>
CRUD Mahasiswa
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.php">
<i class="fas fa-home me-1"></i> Home
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="create.php">
<i class="fas fa-plus me-1"></i> Tambah Data
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
📁 File: includes/footer.php
</div> <!-- End container -->
<footer class="bg-dark text-white text-center py-3 mt-5">
<div class="container">
<p class="mb-0">© 2024 CRUD Mahasiswa. Built with PHP & Bootstrap.</p>
</div>
</footer>
<!-- Bootstrap 5 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JS -->
<script src="assets/js/script.js"></script>
</body>
</html>
📖 READ: Menampilkan Data (Index Page)
Halaman utama untuk menampilkan semua data mahasiswa dengan fitur search dan pagination:
📁 File: index.php
<?php
$page_title = "Data Mahasiswa - CRUD PHP Bootstrap";
include_once 'config/database.php';
include_once 'includes/header.php';
// Inisialisasi database
$database = new Database();
$db = $database->getConnection();
// Pagination setup
$records_per_page = 5;
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$offset = ($page - 1) * $records_per_page;
// Search functionality
$search = isset($_GET['search']) ? $_GET['search'] : '';
$search_condition = '';
$params = [];
if (!empty($search)) {
$search_condition = "WHERE nama LIKE :search OR nim LIKE :search OR jurusan LIKE :search";
$params[':search'] = "%$search%";
}
// Count total records
$count_query = "SELECT COUNT(*) as total FROM mahasiswa $search_condition";
$count_stmt = $db->prepare($count_query);
$count_stmt->execute($params);
$total_records = $count_stmt->fetch()['total'];
$total_pages = ceil($total_records / $records_per_page);
// Fetch data with pagination
$query = "SELECT * FROM mahasiswa $search_condition ORDER BY created_at DESC LIMIT :limit OFFSET :offset";
$stmt = $db->prepare($query);
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value);
}
$stmt->bindValue(':limit', $records_per_page, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$mahasiswa = $stmt->fetchAll();
?>
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-users me-2"></i>Data Mahasiswa</h2>
<a href="create.php" class="btn btn-primary">
<i class="fas fa-plus me-1"></i> Tambah Mahasiswa
</a>
</div>
<!-- Search Form -->
<div class="card mb-4">
<div class="card-body">
<form method="GET" class="row g-3">
<div class="col-md-10">
<input type="text" class="form-control" name="search"
placeholder="Cari berdasarkan nama, NIM, atau jurusan..."
value="<?php echo htmlspecialchars($search); ?>">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-outline-primary w-100">
<i class="fas fa-search me-1"></i> Cari
</button>
</div>
</form>
</div>
</div>
<!-- Data Table -->
<div class="card">
<div class="card-body">
<?php if (count($mahasiswa) > 0): ?>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>#</th>
<th>NIM</th>
<th>Nama</th>
<th>Email</th>
<th>Jurusan</th>
<th>Tanggal Lahir</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<?php
$no = $offset + 1;
foreach ($mahasiswa as $row):
?>
<tr>
<td><?php echo $no++; ?></td>
<td><span class="badge bg-info"><?php echo htmlspecialchars($row['nim']); ?></span></td>
<td><?php echo htmlspecialchars($row['nama']); ?></td>
<td><?php echo htmlspecialchars($row['email']); ?></td>
<td><?php echo htmlspecialchars($row['jurusan']); ?></td>
<td><?php echo date('d/m/Y', strtotime($row['tanggal_lahir'])); ?></td>
<td>
<div class="btn-group" role="group">
<a href="edit.php?id=<?php echo $row['id']; ?>"
class="btn btn-sm btn-warning">
<i class="fas fa-edit"></i>
</a>
<a href="delete.php?id=<?php echo $row['id']; ?>"
class="btn btn-sm btn-danger"
onclick="return confirm('Yakin ingin menghapus data ini?')">
<i class="fas fa-trash"></i>
</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<?php if ($page > 1): ?>
<li class="page-item">
<a class="page-link" href="?page=<?php echo ($page-1); ?>&search=<?php echo urlencode($search); ?>">
<i class="fas fa-chevron-left"></i> Previous
</a>
</li>
<?php endif; ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?php echo ($i == $page) ? 'active' : ''; ?>">
<a class="page-link" href="?page=<?php echo $i; ?>&search=<?php echo urlencode($search); ?>">
<?php echo $i; ?>
</a>
</li>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<li class="page-item">
<a class="page-link" href="?page=<?php echo ($page+1); ?>&search=<?php echo urlencode($search); ?>">
Next <i class="fas fa-chevron-right"></i>
</a>
</li>
<?php endif; ?>
</ul>
</nav>
<?php endif; ?>
<?php else: ?>
<div class="text-center py-5">
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Tidak ada data mahasiswa</h5>
<p class="text-muted">Silakan tambah data mahasiswa baru</p>
<a href="create.php" class="btn btn-primary">
<i class="fas fa-plus me-1"></i> Tambah Mahasiswa
</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php include_once 'includes/footer.php'; ?>
➕ CREATE: Menambah Data Baru
Form untuk menambahkan data mahasiswa baru dengan validasi yang lengkap:
📁 File: create.php
<?php
$page_title = "Tambah Mahasiswa - CRUD PHP Bootstrap";
include_once 'config/database.php';
include_once 'includes/header.php';
$database = new Database();
$db = $database->getConnection();
$errors = [];
$success = false;
if ($_POST) {
// Validasi input
$nim = trim($_POST['nim'] ?? '');
$nama = trim($_POST['nama'] ?? '');
$email = trim($_POST['email'] ?? '');
$jurusan = trim($_POST['jurusan'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$tanggal_lahir = $_POST['tanggal_lahir'] ?? '';
// Validasi required fields
if (empty($nim)) $errors[] = "NIM harus diisi";
if (empty($nama)) $errors[] = "Nama harus diisi";
if (empty($email)) $errors[] = "Email harus diisi";
if (empty($jurusan)) $errors[] = "Jurusan harus diisi";
if (empty($tanggal_lahir)) $errors[] = "Tanggal lahir harus diisi";
// Validasi format email
if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Format email tidak valid";
}
// Validasi NIM unik
if (!empty($nim)) {
$check_query = "SELECT COUNT(*) as count FROM mahasiswa WHERE nim = :nim";
$check_stmt = $db->prepare($check_query);
$check_stmt->bindParam(':nim', $nim);
$check_stmt->execute();
if ($check_stmt->fetch()['count'] > 0) {
$errors[] = "NIM sudah terdaftar";
}
}
// Jika tidak ada error, simpan data
if (empty($errors)) {
try {
$query = "INSERT INTO mahasiswa (nim, nama, email, jurusan, alamat, tanggal_lahir)
VALUES (:nim, :nama, :email, :jurusan, :alamat, :tanggal_lahir)";
$stmt = $db->prepare($query);
$stmt->bindParam(':nim', $nim);
$stmt->bindParam(':nama', $nama);
$stmt->bindParam(':email', $email);
$stmt->bindParam(':jurusan', $jurusan);
$stmt->bindParam(':alamat', $alamat);
$stmt->bindParam(':tanggal_lahir', $tanggal_lahir);
if ($stmt->execute()) {
$success = true;
// Reset form
$nim = $nama = $email = $jurusan = $alamat = $tanggal_lahir = '';
}
} catch (PDOException $e) {
$errors[] = "Error: " . $e->getMessage();
}
}
}
?>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">
<i class="fas fa-user-plus me-2"></i>
Tambah Data Mahasiswa
</h4>
</div>
<div class="card-body">
<!-- Success Alert -->
<?php if ($success): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle me-2"></i>
Data mahasiswa berhasil ditambahkan!
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<!-- Error Alerts -->
<?php if (!empty($errors)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Terjadi kesalahan:</strong>
<ul class="mb-0 mt-2">
<?php foreach ($errors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" novalidate>
<div class="row">
<div class="col-md-6 mb-3">
<label for="nim" class="form-label">
<i class="fas fa-id-card me-1"></i> NIM <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="nim" name="nim"
value="<?php echo htmlspecialchars($nim ?? ''); ?>"
placeholder="Contoh: 2021001" required>
</div>
<div class="col-md-6 mb-3">
<label for="nama" class="form-label">
<i class="fas fa-user me-1"></i> Nama Lengkap <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="nama" name="nama"
value="<?php echo htmlspecialchars($nama ?? ''); ?>"
placeholder="Masukkan nama lengkap" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">
<i class="fas fa-envelope me-1"></i> Email <span class="text-danger">*</span>
</label>
<input type="email" class="form-control" id="email" name="email"
value="<?php echo htmlspecialchars($email ?? ''); ?>"
placeholder="contoh@email.com" required>
</div>
<div class="col-md-6 mb-3">
<label for="jurusan" class="form-label">
<i class="fas fa-graduation-cap me-1"></i> Jurusan <span class="text-danger">*</span>
</label>
<select class="form-select" id="jurusan" name="jurusan" required>
<option value="">Pilih Jurusan</option>
<option value="Teknik Informatika" <?php echo ($jurusan ?? '') == 'Teknik Informatika' ? 'selected' : ''; ?>>Teknik Informatika</option>
<option value="Sistem Informasi" <?php echo ($jurusan ?? '') == 'Sistem Informasi' ? 'selected' : ''; ?>>Sistem Informasi</option>
<option value="Teknik Komputer" <?php echo ($jurusan ?? '') == 'Teknik Komputer' ? 'selected' : ''; ?>>Teknik Komputer</option>
<option value="Manajemen Informatika" <?php echo ($jurusan ?? '') == 'Manajemen Informatika' ? 'selected' : ''; ?>>Manajemen Informatika</option>
</select>
</div>
</div>
<div class="mb-3">
<label for="tanggal_lahir" class="form-label">
<i class="fas fa-calendar me-1"></i> Tanggal Lahir <span class="text-danger">*</span>
</label>
<input type="date" class="form-control" id="tanggal_lahir" name="tanggal_lahir"
value="<?php echo htmlspecialchars($tanggal_lahir ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="alamat" class="form-label">
<i class="fas fa-map-marker-alt me-1"></i> Alamat
</label>
<textarea class="form-control" id="alamat" name="alamat" rows="3"
placeholder="Masukkan alamat lengkap"><?php echo htmlspecialchars($alamat ?? ''); ?></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">
<i class="fas fa-arrow-left me-1"></i> Kembali
</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-1"></i> Simpan Data
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?php include_once 'includes/footer.php'; ?>
✏️ UPDATE: Mengedit Data
Halaman untuk mengedit data mahasiswa yang sudah ada:
📁 File: edit.php
<?php
$page_title = "Edit Mahasiswa - CRUD PHP Bootstrap";
include_once 'config/database.php';
include_once 'includes/header.php';
$database = new Database();
$db = $database->getConnection();
$errors = [];
$success = false;
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
// Ambil data mahasiswa berdasarkan ID
if ($id > 0) {
$query = "SELECT * FROM mahasiswa WHERE id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $id);
$stmt->execute();
$mahasiswa = $stmt->fetch();
if (!$mahasiswa) {
header("Location: index.php");
exit();
}
} else {
header("Location: index.php");
exit();
}
if ($_POST) {
// Validasi input
$nim = trim($_POST['nim'] ?? '');
$nama = trim($_POST['nama'] ?? '');
$email = trim($_POST['email'] ?? '');
$jurusan = trim($_POST['jurusan'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$tanggal_lahir = $_POST['tanggal_lahir'] ?? '';
// Validasi required fields
if (empty($nim)) $errors[] = "NIM harus diisi";
if (empty($nama)) $errors[] = "Nama harus diisi";
if (empty($email)) $errors[] = "Email harus diisi";
if (empty($jurusan)) $errors[] = "Jurusan harus diisi";
if (empty($tanggal_lahir)) $errors[] = "Tanggal lahir harus diisi";
// Validasi format email
if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Format email tidak valid";
}
// Validasi NIM unik (kecuali untuk data yang sedang diedit)
if (!empty($nim)) {
$check_query = "SELECT COUNT(*) as count FROM mahasiswa WHERE nim = :nim AND id != :id";
$check_stmt = $db->prepare($check_query);
$check_stmt->bindParam(':nim', $nim);
$check_stmt->bindParam(':id', $id);
$check_stmt->execute();
if ($check_stmt->fetch()['count'] > 0) {
$errors[] = "NIM sudah terdaftar";
}
}
// Jika tidak ada error, update data
if (empty($errors)) {
try {
$query = "UPDATE mahasiswa SET
nim = :nim,
nama = :nama,
email = :email,
jurusan = :jurusan,
alamat = :alamat,
tanggal_lahir = :tanggal_lahir,
updated_at = CURRENT_TIMESTAMP
WHERE id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(':nim', $nim);
$stmt->bindParam(':nama', $nama);
$stmt->bindParam(':email', $email);
$stmt->bindParam(':jurusan', $jurusan);
$stmt->bindParam(':alamat', $alamat);
$stmt->bindParam(':tanggal_lahir', $tanggal_lahir);
$stmt->bindParam(':id', $id);
if ($stmt->execute()) {
$success = true;
// Refresh data
$mahasiswa['nim'] = $nim;
$mahasiswa['nama'] = $nama;
$mahasiswa['email'] = $email;
$mahasiswa['jurusan'] = $jurusan;
$mahasiswa['alamat'] = $alamat;
$mahasiswa['tanggal_lahir'] = $tanggal_lahir;
}
} catch (PDOException $e) {
$errors[] = "Error: " . $e->getMessage();
}
}
}
?>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-warning text-dark">
<h4 class="mb-0">
<i class="fas fa-edit me-2"></i>
Edit Data Mahasiswa
</h4>
</div>
<div class="card-body">
<!-- Success Alert -->
<?php if ($success): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle me-2"></i>
Data mahasiswa berhasil diperbarui!
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<!-- Error Alerts -->
<?php if (!empty($errors)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Terjadi kesalahan:</strong>
<ul class="mb-0 mt-2">
<?php foreach ($errors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" novalidate>
<div class="row">
<div class="col-md-6 mb-3">
<label for="nim" class="form-label">
<i class="fas fa-id-card me-1"></i> NIM <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="nim" name="nim"
value="<?php echo htmlspecialchars($mahasiswa['nim']); ?>"
placeholder="Contoh: 2021001" required>
</div>
<div class="col-md-6 mb-3">
<label for="nama" class="form-label">
<i class="fas fa-user me-1"></i> Nama Lengkap <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="nama" name="nama"
value="<?php echo htmlspecialchars($mahasiswa['nama']); ?>"
placeholder="Masukkan nama lengkap" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">
<i class="fas fa-envelope me-1"></i> Email <span class="text-danger">*</span>
</label>
<input type="email" class="form-control" id="email" name="email"
value="<?php echo htmlspecialchars($mahasiswa['email']); ?>"
placeholder="contoh@email.com" required>
</div>
<div class="col-md-6 mb-3">
<label for="jurusan" class="form-label">
<i class="fas fa-graduation-cap me-1"></i> Jurusan <span class="text-danger">*</span>
</label>
<select class="form-select" id="jurusan" name="jurusan" required>
<option value="">Pilih Jurusan</option>
<option value="Teknik Informatika" <?php echo $mahasiswa['jurusan'] == 'Teknik Informatika' ? 'selected' : ''; ?>>Teknik Informatika</option>
<option value="Sistem Informasi" <?php echo $mahasiswa['jurusan'] == 'Sistem Informasi' ? 'selected' : ''; ?>>Sistem Informasi</option>
<option value="Teknik Komputer" <?php echo $mahasiswa['jurusan'] == 'Teknik Komputer' ? 'selected' : ''; ?>>Teknik Komputer</option>
<option value="Manajemen Informatika" <?php echo $mahasiswa['jurusan'] == 'Manajemen Informatika' ? 'selected' : ''; ?>>Manajemen Informatika</option>
</select>
</div>
</div>
<div class="mb-3">
<label for="tanggal_lahir" class="form-label">
<i class="fas fa-calendar me-1"></i> Tanggal Lahir <span class="text-danger">*</span>
</label>
<input type="date" class="form-control" id="tanggal_lahir" name="tanggal_lahir"
value="<?php echo htmlspecialchars($mahasiswa['tanggal_lahir']); ?>" required>
</div>
<div class="mb-3">
<label for="alamat" class="form-label">
<i class="fas fa-map-marker-alt me-1"></i> Alamat
</label>
<textarea class="form-control" id="alamat" name="alamat" rows="3"
placeholder="Masukkan alamat lengkap"><?php echo htmlspecialchars($mahasiswa['alamat']); ?></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">
<i class="fas fa-arrow-left me-1"></i> Kembali
</a>
<button type="submit" class="btn btn-warning">
<i class="fas fa-save me-1"></i> Update Data
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?php include_once 'includes/footer.php'; ?>
🗑️ DELETE: Menghapus Data
Script untuk menghapus data mahasiswa dengan konfirmasi:
📁 File: delete.php
<?php
include_once 'config/database.php';
$database = new Database();
$db = $database->getConnection();
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id > 0) {
try {
// Cek apakah data exists
$check_query = "SELECT nama FROM mahasiswa WHERE id = :id";
$check_stmt = $db->prepare($check_query);
$check_stmt->bindParam(':id', $id);
$check_stmt->execute();
$mahasiswa = $check_stmt->fetch();
if ($mahasiswa) {
// Hapus data
$delete_query = "DELETE FROM mahasiswa WHERE id = :id";
$delete_stmt = $db->prepare($delete_query);
$delete_stmt->bindParam(':id', $id);
if ($delete_stmt->execute()) {
// Redirect dengan success message
header("Location: index.php?deleted=1&nama=" . urlencode($mahasiswa['nama']));
exit();
} else {
// Redirect dengan error message
header("Location: index.php?error=delete_failed");
exit();
}
} else {
// Data tidak ditemukan
header("Location: index.php?error=not_found");
exit();
}
} catch (PDOException $e) {
// Redirect dengan error message
header("Location: index.php?error=database_error");
exit();
}
} else {
// ID tidak valid
header("Location: index.php?error=invalid_id");
exit();
}
?>
🎨 Custom CSS untuk Styling
Tambahkan styling custom untuk mempercantik tampilan aplikasi:
📁 File: assets/css/style.css
/* Custom Styles for CRUD Application */
:root {
--primary-color: #0d6efd;
--secondary-color: #6c757d;
--success-color: #198754;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #0dcaf0;
}
body {
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* Navbar Styling */
.navbar-brand {
font-weight: 600;
font-size: 1.5rem;
}
/* Card Styling */
.card {
border: none;
border-radius: 15px;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.card-header {
border-radius: 15px 15px 0 0 !important;
border-bottom: none;
padding: 1.5rem;
}
/* Table Styling */
.table {
border-radius: 10px;
overflow: hidden;
}
.table thead th {
border-top: none;
font-weight: 600;
text-transform: uppercase;
font-size: 0.875rem;
letter-spacing: 0.5px;
}
.table tbody tr {
transition: all 0.3s ease;
}
.table tbody tr:hover {
background-color: rgba(13, 110, 253, 0.05);
transform: scale(1.01);
}
/* Button Styling */
.btn {
border-radius: 8px;
font-weight: 500;
padding: 0.5rem 1rem;
transition: all 0.3s ease;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.btn-group .btn {
margin: 0 2px;
}
/* Form Styling */
.form-control, .form-select {
border-radius: 8px;
border: 2px solid #e9ecef;
padding: 0.75rem;
transition: all 0.3s ease;
}
.form-control:focus, .form-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.form-label {
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
}
/* Alert Styling */
.alert {
border: none;
border-radius: 10px;
padding: 1rem 1.5rem;
}
/* Badge Styling */
.badge {
font-size: 0.875rem;
padding: 0.5rem 0.75rem;
border-radius: 6px;
}
/* Pagination Styling */
.pagination .page-link {
border-radius: 8px;
margin: 0 2px;
border: 2px solid #e9ecef;
color: var(--primary-color);
font-weight: 500;
}
.pagination .page-item.active .page-link {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
/* Footer Styling */
footer {
margin-top: auto;
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.table-responsive {
border-radius: 10px;
}
.btn-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.btn-group .btn {
margin: 0;
}
}
/* Loading Animation */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Success Animation */
.fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
⚡ JavaScript untuk Interaktivitas
Tambahkan JavaScript untuk meningkatkan user experience:
📁 File: assets/js/script.js
// CRUD Application JavaScript
document.addEventListener('DOMContentLoaded', function() {
// Auto-hide alerts after 5 seconds
const alerts = document.querySelectorAll('.alert');
alerts.forEach(alert => {
setTimeout(() => {
if (alert && alert.classList.contains('show')) {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
}
}, 5000);
});
// Form validation
const forms = document.querySelectorAll('form[novalidate]');
forms.forEach(form => {
form.addEventListener('submit', function(event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
});
});
// Confirm delete with better UX
const deleteLinks = document.querySelectorAll('a[href*="delete.php"]');
deleteLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const nama = this.closest('tr').querySelector('td:nth-child(3)').textContent;
if (confirm(`Apakah Anda yakin ingin menghapus data mahasiswa "${nama}"?\n\nTindakan ini tidak dapat dibatalkan.`)) {
// Show loading state
this.innerHTML = '';
this.classList.add('disabled');
// Redirect after short delay for better UX
setTimeout(() => {
window.location.href = this.href;
}, 500);
}
});
});
// Search functionality with debounce
const searchInput = document.querySelector('input[name="search"]');
if (searchInput) {
let searchTimeout;
searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
if (this.value.length >= 3 || this.value.length === 0) {
this.form.submit();
}
}, 500);
});
}
// Auto-format NIM input
const nimInput = document.querySelector('#nim');
if (nimInput) {
nimInput.addEventListener('input', function() {
// Remove non-numeric characters
this.value = this.value.replace(/[^0-9]/g, '');
// Limit to 10 characters
if (this.value.length > 10) {
this.value = this.value.slice(0, 10);
}
});
}
// Auto-capitalize name input
const namaInput = document.querySelector('#nama');
if (namaInput) {
namaInput.addEventListener('input', function() {
// Capitalize first letter of each word
this.value = this.value.replace(/\b\w/g, l => l.toUpperCase());
});
}
// Email validation feedback
const emailInput = document.querySelector('#email');
if (emailInput) {
emailInput.addEventListener('blur', function() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (this.value && !emailRegex.test(this.value)) {
this.classList.add('is-invalid');
// Add or update invalid feedback
let feedback = this.parentNode.querySelector('.invalid-feedback');
if (!feedback) {
feedback = document.createElement('div');
feedback.className = 'invalid-feedback';
this.parentNode.appendChild(feedback);
}
feedback.textContent = 'Format email tidak valid';
} else {
this.classList.remove('is-invalid');
const feedback = this.parentNode.querySelector('.invalid-feedback');
if (feedback) {
feedback.remove();
}
}
});
}
// Smooth scroll for pagination
const paginationLinks = document.querySelectorAll('.pagination a');
paginationLinks.forEach(link => {
link.addEventListener('click', function(e) {
// Add loading state
this.innerHTML = '';
});
});
// Table row click to highlight
const tableRows = document.querySelectorAll('tbody tr');
tableRows.forEach(row => {
row.addEventListener('click', function() {
// Remove previous highlights
tableRows.forEach(r => r.classList.remove('table-active'));
// Add highlight to clicked row
this.classList.add('table-active');
});
});
// Show success message if data was deleted
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('deleted') === '1') {
const nama = urlParams.get('nama');
showToast(`Data mahasiswa "${nama}" berhasil dihapus`, 'success');
// Clean URL
window.history.replaceState({}, document.title, window.location.pathname);
}
// Toast notification function
function showToast(message, type = 'info') {
const toastContainer = getOrCreateToastContainer();
const toast = document.createElement('div');
toast.className = `toast align-items-center text-white bg-${type} border-0`;
toast.setAttribute('role', 'alert');
toast.innerHTML = `
`; toastContainer.appendChild(toast); const bsToast = new bootstrap.Toast(toast); bsToast.show(); // Remove toast element after it’s hidden toast.addEventListener(‘hidden.bs.toast’, () => { toast.remove(); }); } function getOrCreateToastContainer() { let container = document.querySelector(‘.toast-container’); if (!container) { container = document.createElement(‘div’); container.className = ‘toast-container position-fixed bottom-0 end-0 p-3’; document.body.appendChild(container); } return container; } });
🔒 Security Best Practices
🛡️ Keamanan Aplikasi Web
Gunakan prepared statements dengan PDO untuk mencegah SQL injection
Selalu gunakan htmlspecialchars() untuk output data user
Validasi semua input di server-side, jangan hanya client-side
Implementasikan CSRF tokens untuk form submissions
Jangan tampilkan error messages yang expose informasi sensitif
🚀 Deployment dan Optimisasi
📦 Persiapan Production
Sebelum deploy ke production, pastikan Anda melakukan optimisasi berikut:
🔧 Konfigurasi Production
- Database: Ganti credentials database dengan yang production
- Error Reporting: Matikan error reporting di production
- HTTPS: Pastikan menggunakan SSL certificate
- Backup: Setup automated backup untuk database
⚡ Performance Optimization
- Caching: Implement caching untuk query yang sering digunakan
- Compression: Enable GZIP compression di server
- CDN: Gunakan CDN untuk Bootstrap dan Font Awesome
- Database Indexing: Tambahkan index pada kolom yang sering di-query
🔗 Artikel Terkait
Untuk memperdalam pemahaman PHP dan web development, baca juga artikel-artikel berikut:
- Kelas Programmer: PHP Web Development Bootcamp – Belajar PHP dari Dasar hingga Mahir – Program intensif PHP web development dengan project-based learning
- Tutorial PHP MySQL untuk Pemula: Panduan Lengkap Database Integration – Tutorial komprehensif PHP MySQL untuk pemula dengan banyak contoh
- Panduan Bootstrap Framework Lengkap: Responsive Web Design Made Easy – Deep dive ke Bootstrap framework untuk responsive design
❓ FAQ (Frequently Asked Questions)
CRUD adalah singkatan dari Create, Read, Update, Delete – empat operasi dasar dalam pengelolaan data. CRUD penting karena hampir semua aplikasi web membutuhkan kemampuan untuk menambah, membaca, mengubah, dan menghapus data. Ini adalah foundation dari sistem manajemen data.
PDO (PHP Data Objects) lebih fleksibel karena mendukung multiple database engines, memiliki API yang konsisten, dan built-in support untuk prepared statements. PDO juga lebih secure dan mudah untuk switch antar database jika diperlukan.
Gunakan prepared statements dengan parameter binding. Jangan pernah concatenate user input langsung ke SQL query. Contoh: $stmt->bindParam(':nama', $nama)
lebih aman daripada "SELECT * FROM users WHERE nama = '$nama'"
.
Tidak wajib, tapi sangat direkomendasikan. Bootstrap mempercepat development dengan menyediakan komponen UI yang sudah jadi dan responsive. Anda bisa menggunakan framework CSS lain seperti Tailwind, Bulma, atau bahkan custom CSS.
Tambahkan input type=”file” di form, set enctype=”multipart/form-data”, dan handle file upload di PHP menggunakan $_FILES. Pastikan validasi file type, size, dan simpan file di folder yang aman dengan nama yang di-sanitize.
Gunakan LIMIT dan OFFSET dalam SQL query, hitung total records untuk pagination info, dan implement caching jika diperlukan. Untuk data sangat besar, consider cursor-based pagination atau infinite scrolling.
Aplikasi ini adalah foundation yang baik, tapi untuk production perlu tambahan: authentication/authorization, logging, error handling yang robust, rate limiting, input sanitization yang lebih ketat, dan security headers.
Untuk Excel, gunakan library seperti PhpSpreadsheet. Untuk PDF, gunakan TCPDF atau mPDF. Buat endpoint terpisah untuk export, query data yang diperlukan, dan generate file sesuai format yang diminta user.
🎓 Kesimpulan
Selamat! Anda telah berhasil membuat CRUD sederhana PHP Bootstrap yang lengkap dan functional. Aplikasi ini mencakup semua operasi dasar CRUD dengan interface yang responsive dan user-friendly.
Dari tutorial ini, Anda telah mempelajari:
- Database Design dan setup MySQL yang proper
- PHP PDO untuk koneksi database yang secure
- Bootstrap 5 untuk responsive UI design
- Form Validation dan error handling
- Security Best Practices untuk web applications
- Pagination dan Search functionality
- Clean Code Structure dan organization
Aplikasi CRUD ini bisa menjadi foundation untuk project yang lebih kompleks seperti sistem manajemen sekolah, inventory management, atau customer relationship management (CRM). Terus kembangkan dengan menambahkan fitur authentication, role management, dan advanced features lainnya!
🚀 Ready to Build Your CRUD Application?
Mulai journey web development Anda dengan project CRUD yang praktis!