๐ Apa itu Perpustakaan Digital?
Perpustakaan Digital adalah sistem informasi yang mengelola koleksi buku, peminjaman, pengembalian, dan data anggota secara elektronik menggunakan database sebagai penyimpan data utama.
Komponen Utama Sistem:
Database (MySQL)
Menyimpan seluruh data: buku, anggota, transaksi peminjaman & pengembalian.
Koneksi (PHP/PDO)
Menjembatani aplikasi web dengan database server menggunakan PHP.
CRUD Operations
Create, Read, Update, Delete โ 4 operasi dasar manipulasi data.
๐๏ธ Arsitektur Sistem
Alur: User โ Browser โ PHP (Server) โ MySQL (Database) โ Response kembali ke User
๐ ๏ธ Tools yang Dibutuhkan
Paket Apache + MySQL + PHP untuk development lokal
GUI untuk mengelola database MySQL via browser
Code editor untuk menulis PHP, SQL, HTML
Chrome/Firefox untuk testing aplikasi web
๐๏ธ Pembuatan Database
Langkah 1: Membuat Database
Buka phpMyAdmin atau MySQL CLI, lalu jalankan perintah berikut:
-- Membuat database perpustakaan CREATE DATABASE db_perpustakaan CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- Menggunakan database USE db_perpustakaan;
Langkah 2: Tabel Buku
CREATE TABLE buku ( id_buku INT AUTO_INCREMENT PRIMARY KEY, judul VARCHAR(255) NOT NULL, pengarang VARCHAR(150) NOT NULL, penerbit VARCHAR(150), tahun_terbit YEAR, isbn VARCHAR(20) UNIQUE, kategori ENUM('Fiksi','Non-Fiksi','Sains', 'Teknologi','Sejarah','Lainnya'), stok INT DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
Langkah 3: Tabel Anggota
CREATE TABLE anggota ( id_anggota INT AUTO_INCREMENT PRIMARY KEY, nama VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE, no_telepon VARCHAR(15), alamat TEXT, tgl_daftar DATE DEFAULT (CURRENT_DATE) );
Langkah 4: Tabel Peminjaman
CREATE TABLE peminjaman ( id_pinjam INT AUTO_INCREMENT PRIMARY KEY, id_anggota INT NOT NULL, id_buku INT NOT NULL, tgl_pinjam DATE NOT NULL, tgl_kembali DATE, status ENUM('Dipinjam','Dikembalikan','Terlambat') DEFAULT 'Dipinjam', FOREIGN KEY (id_anggota) REFERENCES anggota(id_anggota) ON DELETE RESTRICT, FOREIGN KEY (id_buku) REFERENCES buku(id_buku) ON DELETE RESTRICT );
๐ Entity Relationship Diagram (ERD)
ANGGOTA
PEMINJAMAN
BUKU
๐ Penjelasan Tipe Data
| Tipe Data | Kegunaan | Contoh |
|---|---|---|
| INT | Bilangan bulat (ID, stok) | 1, 42, 100 |
| VARCHAR(n) | Teks dengan panjang maks n | "Harry Potter" |
| TEXT | Teks panjang (alamat, deskripsi) | "Jl. Merdeka No.1..." |
| DATE / YEAR | Tanggal atau tahun | 2024-01-15, 2023 |
| ENUM | Nilai terbatas (pilihan) | 'Fiksi', 'Sains' |
| TIMESTAMP | Tanggal + waktu otomatis | 2024-01-15 08:30:00 |
๐ Koneksi Database
Metode 1: MySQLi (Prosedural)
Cara paling sederhana, cocok untuk pemula.
// File: koneksi_mysqli.php <?php $host = "localhost"; $user = "root"; $pass = ""; $dbname = "db_perpustakaan"; // Membuat koneksi $conn = mysqli_connect($host, $user, $pass, $dbname); // Cek koneksi berhasil atau tidak if (!$conn) { die("Koneksi gagal: " . mysqli_connect_error()); } echo "Koneksi berhasil!"; ?>
Metode 2: PDO (Direkomendasikan) โญ
Lebih aman, mendukung prepared statements, dan bisa digunakan untuk berbagai database.
// File: koneksi.php <?php $host = "localhost"; $dbname = "db_perpustakaan"; $user = "root"; $pass = ""; try { $pdo = new PDO( "mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass, [ 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()); } ?>
โ ๏ธ Perbandingan MySQLi vs PDO
| Fitur | MySQLi | PDO |
|---|---|---|
| Database Support | MySQL saja | 12+ database |
| Prepared Statements | โ Ya | โ Ya |
| Named Parameters | โ Tidak | โ Ya |
| OOP & Prosedural | โ Keduanya | OOP saja |
๐ Keamanan: Prepared Statements
Prepared Statements mencegah SQL Injection โ serangan paling umum pada aplikasi web.
โ SALAH (Rentan SQL Injection)
WHERE judul = '$_GET['q']'";
$result = mysqli_query($conn, $sql);
โ BENAR (Prepared Statement)
"SELECT * FROM buku
WHERE judul = :judul"
);
$stmt->execute([':judul' => $q]);
๐ Operasi CRUD Lengkap
CREATE โ Tambah Data Buku
// File: tambah_buku.php <?php require_once 'koneksi.php'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $sql = "INSERT INTO buku (judul, pengarang, penerbit, tahun_terbit, isbn, kategori, stok) VALUES (:judul, :pengarang, :penerbit, :tahun, :isbn, :kategori, :stok)"; $stmt = $pdo->prepare($sql); $stmt->execute([ ':judul' => $_POST['judul'], ':pengarang' => $_POST['pengarang'], ':penerbit' => $_POST['penerbit'], ':tahun' => $_POST['tahun'], ':isbn' => $_POST['isbn'], ':kategori' => $_POST['kategori'], ':stok' => $_POST['stok'] ]); echo "Buku berhasil ditambahkan!"; } ?>
READ โ Tampilkan Data Buku
// File: daftar_buku.php <?php require_once 'koneksi.php'; // Ambil semua buku $stmt = $pdo->query( "SELECT * FROM buku ORDER BY judul ASC" ); $buku_list = $stmt->fetchAll(); // Cari buku berdasarkan judul $keyword = $_GET['cari'] ?? ''; if ($keyword) { $stmt = $pdo->prepare( "SELECT * FROM buku WHERE judul LIKE :keyword OR pengarang LIKE :keyword" ); $stmt->execute([ ':keyword' => "%$keyword%" ]); $buku_list = $stmt->fetchAll(); } ?>
UPDATE โ Edit Data Buku
// File: edit_buku.php <?php require_once 'koneksi.php'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $sql = "UPDATE buku SET judul = :judul, pengarang = :pengarang, penerbit = :penerbit, tahun_terbit = :tahun, isbn = :isbn, kategori = :kategori, stok = :stok WHERE id_buku = :id"; $stmt = $pdo->prepare($sql); $stmt->execute([ ':judul' => $_POST['judul'], ':pengarang' => $_POST['pengarang'], ':penerbit' => $_POST['penerbit'], ':tahun' => $_POST['tahun'], ':isbn' => $_POST['isbn'], ':kategori' => $_POST['kategori'], ':stok' => $_POST['stok'], ':id' => $_POST['id_buku'] ]); echo "Buku berhasil diperbarui!"; } ?>
DELETE โ Hapus Data Buku
// File: hapus_buku.php <?php require_once 'koneksi.php'; $id = $_GET['id'] ?? null; if ($id) { // Cek apakah buku sedang dipinjam $cek = $pdo->prepare( "SELECT COUNT(*) FROM peminjaman WHERE id_buku = :id AND status = 'Dipinjam'" ); $cek->execute([':id' => $id]); if ($cek->fetchColumn() > 0) { echo "Tidak bisa dihapus! Buku sedang dipinjam."; } else { $stmt = $pdo->prepare( "DELETE FROM buku WHERE id_buku = :id" ); $stmt->execute([':id' => $id]); echo "Buku berhasil dihapus!"; } } ?>
๐ Relasi Antar Tabel & Query Lanjutan
JOIN โ Menggabungkan Data dari Beberapa Tabel
Untuk menampilkan data peminjaman beserta nama anggota dan judul buku:
SELECT p.id_pinjam, a.nama AS nama_anggota, b.judul AS judul_buku, p.tgl_pinjam, p.tgl_kembali, p.status FROM peminjaman p INNER JOIN anggota a ON p.id_anggota = a.id_anggota INNER JOIN buku b ON p.id_buku = b.id_buku ORDER BY p.tgl_pinjam DESC;
Proses Peminjaman (PHP)
// File: proses_pinjam.php <?php require_once 'koneksi.php'; try { // Mulai transaksi database $pdo->beginTransaction(); // 1. Cek stok buku $cek = $pdo->prepare( "SELECT stok FROM buku WHERE id_buku = :id FOR UPDATE" ); $cek->execute([':id' => $_POST['id_buku']]); $stok = $cek->fetchColumn(); if ($stok < 1) { throw new Exception("Stok habis!"); } // 2. Catat peminjaman $pdo->prepare( "INSERT INTO peminjaman (id_anggota, id_buku, tgl_pinjam) VALUES (:anggota, :buku, CURDATE())" )->execute([ ':anggota' => $_POST['id_anggota'], ':buku' => $_POST['id_buku'] ]); // 3. Kurangi stok $pdo->prepare( "UPDATE buku SET stok = stok - 1 WHERE id_buku = :id" )->execute([':id' => $_POST['id_buku']]); // Commit semua perubahan $pdo->commit(); echo "Peminjaman berhasil!"; } catch (Exception $e) { $pdo->rollBack(); echo "Error: " . $e->getMessage(); } ?>
๐ก Mengapa Menggunakan Transaction?
- โ Atomicity โ Semua operasi berhasil atau semua dibatalkan
- โ Consistency โ Data selalu dalam keadaan valid
- โ Contoh โ Jika INSERT peminjaman berhasil tapi UPDATE stok gagal, maka INSERT juga dibatalkan (rollback)
๐ Query Statistik Berguna
-- Buku paling sering dipinjam SELECT b.judul, COUNT(*) AS total_pinjam FROM peminjaman p JOIN buku b ON p.id_buku = b.id_buku GROUP BY b.id_buku ORDER BY total_pinjam DESC LIMIT 5;
-- Anggota dengan peminjaman terlambat SELECT a.nama, COUNT(*) AS jml_terlambat FROM peminjaman p JOIN anggota a ON p.id_anggota = a.id_anggota WHERE p.status = 'Terlambat' GROUP BY a.id_anggota;
๐ป Script Fungsi CRUD Siap Pakai
Kumpulan fungsi PHP dan JavaScript yang dapat langsung digunakan dalam proyek Perpustakaan Digital. Semua fungsi menggunakan prepared statements untuk keamanan maksimal.
๐ฆ Helper Functions (Fungsi Pembantu)
// File: helper.php <?php // 1. Redirect dengan pesan function redirect($url, $msg = '', $type = 'success') { if ($msg) { $_SESSION['message'] = $msg; $_SESSION['msg_type'] = $type; } header("Location: $url"); exit(); } // 2. Validasi input function validate_input($data) { return htmlspecialchars( trim($data), ENT_QUOTES, 'UTF-8' ); } // 3. Format tanggal Indonesia function format_date($date) { $months = [ 'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember' ]; $dt = new DateTime($date); $day = $dt->format('d'); $month = $months[(int)$dt->format('m') - 1]; $year = $dt->format('Y'); return "$day $month $year"; } // 4. Cek data duplikat function is_duplicate($pdo, $table, $field, $value, $exclude_id = null) { $sql = "SELECT COUNT(*) FROM $table WHERE $field = :$field"; if ($exclude_id) $sql .= " AND id != :id"; $stmt = $pdo->prepare($sql); $params = [':' . $field => $value]; if ($exclude_id) $params[':id'] = $exclude_id; $stmt->execute($params); return $stmt->fetchColumn() > 0; } ?>
๐ฏ Class BukuManager (Manajemen Buku)
// File: BukuManager.php <?php class BukuManager { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // CREATE - Tambah buku baru public function tambah($data) { $sql = "INSERT INTO buku (judul, pengarang, penerbit, tahun_terbit, isbn, kategori, stok) VALUES (:judul, :pengarang, :penerbit, :tahun, :isbn, :kategori, :stok)"; $stmt = $this->pdo->prepare($sql); return $stmt->execute($data); } // READ - Ambil semua buku public function ambil_semua($sortBy = 'judul') { $sql = "SELECT * FROM buku ORDER BY $sortBy ASC"; return $this->pdo->query($sql) ->fetchAll(); } // READ - Cari buku public function cari($keyword) { $sql = "SELECT * FROM buku WHERE judul LIKE :keyword OR pengarang LIKE :keyword ORDER BY judul ASC"; $stmt = $this->pdo->prepare($sql); $stmt->execute([ ':keyword' => "%$keyword%" ]); return $stmt->fetchAll(); } // UPDATE - Edit buku public function ubah($id, $data) { $sql = "UPDATE buku SET judul = :judul, pengarang = :pengarang, penerbit = :penerbit, tahun_terbit = :tahun, isbn = :isbn, kategori = :kategori, stok = :stok WHERE id_buku = :id"; $data[':id'] = $id; $stmt = $this->pdo->prepare($sql); return $stmt->execute($data); } // DELETE - Hapus buku public function hapus($id) { $cek = $this->pdo->prepare( "SELECT COUNT(*) FROM peminjaman WHERE id_buku = :id AND status = 'Dipinjam'" ); $cek->execute([':id' => $id]); if ($cek->fetchColumn() > 0) { throw new Exception( "Tidak bisa hapus, buku masih dipinjam" ); } $stmt = $this->pdo->prepare( "DELETE FROM buku WHERE id_buku = :id" ); return $stmt->execute([':id' => $id]); } // Statistik - Total buku public function total_buku() { $res = $this->pdo->query( "SELECT COUNT(*) FROM buku" ); return $res->fetchColumn(); } } ?>
๐ง JavaScript Utility (Client-Side)
// File: app.js - Fungsi Helper JavaScript // 1. AJAX Request Helper async function apiCall(endpoint, method = 'GET', data = null) { const options = { method: method, headers: { 'Content-Type': 'application/json' } }; if (data) options.body = JSON.stringify(data); const response = await fetch(endpoint, options); return response.json(); } // 2. Format tanggal (ID) function formatDate(date) { const months = ['Jan','Feb','Mar', 'Apr','Mei','Jun', 'Jul','Ags','Sep', 'Okt','Nov','Des']; const d = new Date(date); return d.getDate() + ' ' + months[d.getMonth()] + ' ' + d.getFullYear(); } // 3. Validasi Email function isValidEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); } // 4. Export ke CSV function exportCSV(data, filename) { let csv = Object.keys(data[0]).join(',') + '\n'; data.forEach(row => { csv += Object.values(row).join(',') + '\n'; }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); }