Menghubungkan aplikasi ke database lewat konfigurasi Database, mengenal Query Builder untuk kueri fleksibel, lalu membangun class Model dengan CRUD, timestamps otomatis, dan validation bawaan untuk table posts.

Setelah di episode 5 kita membangun sistem templating yang rapi, sekarang saatnya masuk ke sisi "M" dari MVC: Model dan Database. Di sinilah data benar-benar hidup — disimpan, diambil, diubah, dan dihapus dari database relasional.
Mengapa ini penting? Karena hampir semua aplikasi nyata adalah aplikasi CRUD (Create, Read, Update, Delete) di atas data. CodeIgniter 4 memberi dua lapisan akses data: Query Builder untuk kueri fleksibel, dan class Model yang membungkus CRUD dengan validation dan events. Di episode ini kalian akan menguasai keduanya.
Koneksi database diatur di app/Config/Database.php dan bisa dioverride lewat .env:
database.default.hostname = localhost
database.default.database = ci_blog
database.default.username = root
database.default.password = rahasia
database.default.DBDriver = MySQLi
database.default.port = 3306DBDriver menentukan driver: MySQLi untuk MySQL/MariaDB, Postgre untuk PostgreSQL, SQLite3 untuk SQLite. Buat database-nya:
mysql -u root -p -e "CREATE DATABASE ci_blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"Warning
Jangan pernah menaruh kredensial database langsung di Database.php. Selalu override lewat .env — file ini tidak ter-commit ke git, sehingga password tidak bocor dan tiap mesin punya kredensial sendiri.
Query Builder adalah cara berinteraksi dengan database tanpa menulis SQL mentah. Ambil koneksi lalu mulai membangun kueri:
$db = \Config\Database::connect();
$builder = $db->table('posts');
$builder->select('id, title, created_at');
$builder->where('status', 'published');
$builder->orderBy('created_at', 'DESC');
$builder->limit(10);
$posts = $builder->get()->getResultArray();getResultArray() mengembalikan array asosiatif; getResult() mengembalikan objek. Query Builder selalu meng-escape nilai yang dimasukkan lewat where(), sehingga kalian terlindungi dari SQL injection — dibahas detail di episode 18.
// CREATE
$db->table('posts')->insert([
'title' => 'Post baru',
'slug' => 'post-baru',
'status' => 'draft',
]);
// READ
$post = $db->table('posts')->where('id', 1)->get()->getRowArray();
// UPDATE
$db->table('posts')->where('id', 1)->update(['status' => 'published']);
// DELETE
$db->table('posts')->where('id', 1)->delete();Perhatikan bahwa tiap operasi dimulai dari $db->table('posts') — ini membuat "instruksi" yang baru dieksekusi saat get(), insert(), update(), atau delete() dipanggil. Banyak error pemula muncul karena lupa memanggil method eksekusi.
Untuk operasi berulang pada satu tabel, CodeIgniter menyediakan class CodeIgniter\Model. Buat model posts:
<?php
namespace App\Models;
use CodeIgniter\Model;
class PostModel extends Model
{
protected $table = 'posts';
protected $primaryKey = 'id';
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $allowedFields = ['title', 'slug', 'body', 'status'];
protected $returnType = 'array';
}Property kunci:
| Property | Fungsi |
|---|---|
$table | Nama tabel yang dibungkus model |
$primaryKey | Primary key tabel |
$allowedFields | Whitelist kolom yang boleh di-insert/update — di luar ini diabaikan |
$useTimestamps | Isi created_at/updated_at otomatis |
$returnType | array atau object hasil kueri |
$postModel = new \App\Models\PostModel();
// CREATE
$id = $postModel->insert([
'title' => 'Judul pertama',
'slug' => 'judul-pertama',
'body' => 'Isi post',
'status' => 'draft',
]);
// READ - semua dengan filter
$posts = $postModel->where('status', 'published')
->orderBy('created_at', 'DESC')
->findAll();
// READ - satu record
$post = $postModel->find($id);
// UPDATE
$postModel->update($id, ['status' => 'published']);
// DELETE
$postModel->delete($id);Model juga menyediakan findAll(), first(), where() chaining, pagination (paginate(10)), dan masih banyak lagi. Karena ini adalah class, logika akses data bisa di-extend: validasi, events, relasi — kita akan kembangkan di episode 21.
Tip
Jika insert() atau update() gagal tanpa error yang jelas, kemungkinan besar kolom yang dikirim tidak ada di $allowedFields. Model menolak menulis kolom di luar whitelist — ini fitur keamanan yang membingungkan kalau tidak dipahami.
Model bawaan bisa menvalidasi data sebelum insert/update:
protected $validationRules = [
'title' => 'required|max_length[255]',
'slug' => 'required|alpha_dash|is_unique[posts.slug]',
'status' => 'required|in_list[draft,published]',
];Saat insert() dipanggil, data divalidasi otomatis. Jika gagal, $model->errors() berisi pesan error:
if (! $postModel->save($data)) {
$errors = $postModel->errors();
return redirect()->back()->withInput()->with('errors', $errors);
}save() menangani insert atau update secara cerdas — jika ada $data['id'] yang cocok dengan primary key, ia update; jika tidak, ia insert. Validasi dan form handling penuh akan kita dalami di episode 8.
Sekarang gabungkan semuanya dalam controller:
<?php
namespace App\Controllers;
use App\Models\PostModel;
class Posts extends BaseController
{
public function index(): string
{
$postModel = new PostModel();
$data = [
'title' => 'Daftar Post',
'posts' => $postModel->where('status', 'published')
->orderBy('created_at', 'DESC')
->findAll(),
];
return view('posts/index', $data);
}
public function show(int $id): string
{
$post = (new PostModel())->find($id);
if ($post === null) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
return view('posts/show', ['post' => $post]);
}
}PageNotFoundException mengubah akses ke post yang tidak ada menjadi halaman 404 — bukan error kosong. Ini contoh kecil dari error handling yang benar.
Database.php / .env; DBDriver memilih MySQL, PostgreSQL, atau SQLite.$table, $allowedFields, timestamps, return type.PageNotFoundException mengubah data tidak ditemukan menjadi 404 yang benar.Inti yang harus dibawa pulang:
.env, bukan di config file yang ter-commit.$allowedFields — isi whitelist dengan benar.save() untuk insert/update cerdas dan tangkap errors() saat validasi gagal.Di episode 7 selanjutnya kita akan membahas migrations dan seeding — mengelola skema database sebagai kode dengan php spark make:migration, php spark migrate, serta mengisi data awal dengan seeder. Ini praktik yang mengubah cara kalian berkolaborasi dengan tim, karena skema kini tersimpan di git!