Kenapa Kysely, bukan ORM
Temanmu benar menyarankan Kysely, tapi alasannya perlu diluruskan: Kysely mirip Query Builder CI3, bukan Active Record Laravel. Dan perbedaan itu justru menguntungkanmu.
Intisari
- Kysely adalah query builder bertipe, bukan ORM. Tidak ada objek model, tidak ada lazy loading, tidak ada
$artikel->save(). - Bentuknya nyaris identik dengan
$this->db->select()->where()->get()di CI3 β kurva belajarnya pendek. - Bedanya: nama tabel dan nama kolom diperiksa compiler. Salah ketik gagal di editor, bukan di produksi.
- SQL yang dihasilkan bisa ditebak dari kodenya. Tidak ada kueri kejutan seperti di ORM dengan relasi malas.
- Yang tidak kamu dapat: migrasi otomatis dari model, relasi ajaib, dan seeding. Sebagian besar itu tidak kamu butuhkan untuk skema warisan.
Meluruskan istilahnya
Ada tiga hal berbeda yang sering disebut bergantian:
| Pola | Contoh | Ciri |
|---|---|---|
| Active Record | Eloquent (Laravel), Rails | Baris database adalah objek. $a->judul = 'x'; $a->save(); |
| Data Mapper / ORM | Prisma, TypeORM, Doctrine | Objek terpisah dari baris; ada lapisan pemetaan dan sering ada cache identitas |
| Query Builder | Kysely, CI3 Query Builder, Laravel DB facade | Kamu menyusun SQL lewat method. Hasilnya baris biasa, bukan objek berperilaku |
CI3 menamai fiturnya "Active Record" di dokumentasi lama, lalu menggantinya jadi "Query Builder" di CI3 β karena nama pertamanya memang keliru. Yang selama ini kamu pakai adalah query builder. Kysely juga query builder. Perpindahannya bukan perubahan paradigma; ia perubahan sintaks plus penambahan tipe.
Berdampingan
// CodeIgniter 3
$this->db->select('a.id, a.judul, k.nama AS kategori')
->from('artikel a')
->join('kategori k', 'k.id = a.kategori_id', 'left')
->where('a.status', 'terbit')
->where('a.terbit_pada <=', date('Y-m-d H:i:s'))
->order_by('a.terbit_pada', 'DESC')
->limit(20);
$artikel = $this->db->get()->result();
// Kysely
const artikel = await db
.selectFrom("artikel as a")
.leftJoin("kategori as k", "k.id", "a.kategori_id")
.select(["a.id", "a.judul", "k.nama as kategori"])
.where("a.status", "=", "terbit")
.where("a.terbit_pada", "<=", new Date())
.orderBy("a.terbit_pada", "desc")
.limit(20)
.execute();
Baris demi baris hampir sama. Perbedaannya yang penting tidak terlihat di layar:
| Kalau kamu salah ketik⦠| CI3 | Kysely |
|---|---|---|
'a.judl' | Error MySQL saat runtime, di produksi | Garis merah di editor |
'artikl' | Error MySQL saat runtime | Garis merah di editor |
Lupa status = 'terbit' | Draf tersaji ke publik, diam-diam | Sama β tipe tidak menolongmu di sini (lihat Fase 0) |
Membandingkan kolom int dengan string | MySQL diam-diam mengonversi | Error tipe |
Dan hasilnya bertipe. artikel[0].kategori diketahui compiler sebagai string | null
β null karena itu leftJoin. Kysely menurunkan nullability dari jenis join-nya, dan
itu menangkap kelas bug yang di CI3 baru muncul saat ada artikel tanpa kategori.
Kenapa bukan Prisma atau Drizzle
| Kysely | Prisma | Drizzle | |
|---|---|---|---|
| Cocok untuk skema warisan | Sangat β introspeksi, tanpa menuntut bentuk tertentu | Bisa, tapi skemanya jadi sumber kebenaran | Bisa |
| SQL bisa ditebak dari kode | Ya, satu banding satu | Tidak selalu | Sebagian besar ya |
| Runtime tambahan | Tidak ada | Dulu ada engine biner | Tidak ada |
| Kurva belajar dari CI3 | Paling landai | Konsep baru: model, relasi, generator | Landai |
| SQL mentah saat perlu | Mulus lewat sql | Ada, terasa terpisah | Mulus |
| Ukuran bundel | Kecil | Besar | Kecil |
Untuk migrasi dari skema CI3 yang sudah berjalan bertahun-tahun, alasan pertama itu menentukan. Skemamu
punya kolom yang tidak konsisten penamaannya, tabel tanpa foreign key, dan kolom int(11) berisi
timestamp Unix. Kysely tidak peduli β ia mengambil bentuk skemamu apa adanya lewat introspeksi. ORM yang
menuntut model rapi akan memaksamu melawan skemamu sendiri di hari pertama.
Ini bukan penilaian bahwa Prisma buruk. Untuk proyek baru dengan skema yang kamu rancang sendiri, Prisma memberi banyak hal yang Kysely tidak. Tapi kamu tidak sedang membangun proyek baru β kamu sedang memindahkan portal yang sudah hidup, dan yang paling kamu butuhkan adalah alat yang tidak berpendapat tentang skema.
Yang tidak kamu dapat, dan apa gantinya
| Tidak ada di Kysely | Gantinya |
|---|---|
$artikel->kategori->nama (relasi malas) | join eksplisit, atau jsonArrayFrom untuk relasi bersarang |
| Model dengan method | Fungsi di src/lib/artikel.ts |
| Timestamp otomatis | Tulis sendiri, atau DEFAULT CURRENT_TIMESTAMP di kolomnya |
| Soft delete | Kolom dihapus_pada plus fungsi yang selalu menyaringnya |
| Seeding | Skrip biasa yang memakai db |
Baris pertama itu sebenarnya keuntungan. Relasi malas adalah sumber masalah N+1 yang paling umum: satu
loop yang menyentuh $a->kategori diam-diam menghasilkan satu kueri per artikel. Di Kysely
kamu tidak bisa melakukan itu tanpa sadar β kalau kamu butuh kategori, kamu menulis join-nya.
Struktur berkas yang dipakai
src/lib/
βββ db.ts # instance Kysely + pool. Satu untuk seluruh aplikasi
βββ db-types.ts # DIGENERATE oleh kysely-codegen. Jangan disunting
βββ artikel.ts # fungsi kueri artikel
βββ perusahaan.ts
βββ member.ts
Satu aturan yang berlaku sepanjang roadmap: hanya berkas di src/lib/ yang boleh
mengimpor db. Halaman dan komponen memanggil fungsi, bukan menyusun kueri. Alasannya
di Fase 0: supaya filter status = 'terbit' tidak bisa terlupa, dan supaya cache serta
pengukuran bisa dipasang di satu tempat.
Latihan: ambil tiga kueri paling sering dipakai dari model CI3-mu. Tulis ulang ketiganya sebagai Kysely di kertas atau berkas teks β belum perlu dijalankan. Catat setiap tempat di mana kamu ragu bentuk padanannya; daftar itu akan jadi peta belajarmu untuk materi-materi berikutnya di fase ini.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.