ORM (Object-Relational Mapping) adalah lapisan abstraksi di atas driver basis data yang menyatakan skema tabel sebagai kode, mengelola migrasi (skrip perubahan struktur tabel), dan menerbitkan tipe TypeScript agar kueri diketik secara statis. Tiga ORM yang lazim dipakai pada ekosistem Node adalah Prisma, Drizzle, dan TypeORM, masing-masing dengan pendekatan berbeda terhadap deklarasi skema dan generasi kode. Topik ini membandingkan posisi ketiganya di antara driver mentah dan query builder, memperlihatkan bentuk skema masing-masing, dan menetapkan kapan ORM tepat dipakai serta kapan driver mentah lebih sesuai. Setelah membaca topik ini, pembaca dapat memilih ORM yang sesuai kebutuhan proyek dan memahami konsekuensi pemakaiannya.
Lapisan akses basis data#
Akses basis data pada aplikasi Node tersusun atas beberapa lapisan abstraksi. Driver mentah seperti pg dan mongodb membuka koneksi TCP dan mengirim perintah sesuai protokol server. Query builder seperti Knex dan Kysely merakit perintah SQL dari panggilan fungsi, sehingga sintaks SQL tidak ditulis sebagai string mentah. ORM berada di atas keduanya, menyatakan skema tabel sebagai kode deklaratif, mengelola migrasi dari skema, dan menerbitkan tipe yang dipakai kode aplikasi. Diagram berikut menggambarkan posisi ketiga lapisan.
Pemilihan lapisan menentukan pertukaran antara produktivitas dan kendali. ORM memberikan produktivitas tertinggi karena skema, migrasi, dan tipe dikelola bersama, tetapi menyembunyikan detail SQL yang kadang diperlukan untuk kueri kompleks. Driver mentah memberikan kendali penuh atas SQL tetapi menyerahkan penulisan migrasi dan pengetikan hasil ke pembuat aplikasi. Query builder berada di tengah, merakit SQL tanpa menyatakan skema sebagai kode terpisah.
Prisma: skema sebagai sumber kebenaran#
Prisma menyatakan skema pada berkas .prisma dengan sintaks deklaratif khusus, bukan pada kode TypeScript. Berkas ini menjadi sumber kebenaran yang dipakai untuk generate client Prisma, menjalankan migrasi, dan menerbitkan tipe. Pendekatan ini memisahkan deklarasi skema dari kode aplikasi, sehingga skema tetap dapat dibaca tanpa memahami dekorator TypeScript atau konstruktor kelas.
// tidak-dijalankan: butuh prisma CLI dan server PostgreSQL
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("APP_DB_URL")
}
model Catatan {
id Int @id @default(autoincrement())
nama String
skor Int
dibuatPada DateTime @default(now())
@@index([nama])
}Blok generator menentukan bahasa client yang dihasilkan, sedangkan blok datasource menentukan jenis basis data dan URL koneksi dari variabel lingkungan APP_DB_URL. Setiap model menjadi satu tabel pada basis data, dengan atribut seperti @id untuk kunci utama, @default(autoincrement()) untuk nilai otomatis, dan @@index untuk indeks sekunder. Perintah prisma migrate dev membaca berkas ini, membuat migrasi SQL, dan menerapkan ke basis data, sedangkan prisma generate menghasilkan client TypeScript yang mengetik seluruh operasi pada model.
Drizzle: skema sebagai kode TypeScript#
Drizzle menyatakan skema langsung pada kode TypeScript memakai fungsi pembangun seperti pgTable, serial, dan text. Skema menjadi kode biasa yang dapat diimpor dan dipakai pada kueri, sehingga tidak ada generasi client terpisah. Pendekatan ini menarik bagi pengembang yang menginginkan satu sumber kode dan tidak menyukai langkah generate yang menambah berkas ke direktori node_modules.
// tidak-dijalankan: butuh drizzle-orm, drizzle-kit, dan server PostgreSQL
import { pgTable, serial, text, integer, timestamp, index } from 'drizzle-orm/pg-core';
export const catatan = pgTable(
'catatan',
{
id: serial('id').primaryKey(),
nama: text('nama').notNull(),
skor: integer('skor').notNull(),
dibuatPada: timestamp('dibuat_pada').defaultNow()
},
(table) => ({
namaIdx: index('catatan_nama_idx').on(table.nama)
})
);Fungsi pgTable menerima nama tabel dan objek kolom, sedangkan fungsi seperti serial, text, dan integer menentukan tipe kolom. Indeks dideklarasikan pada argumen ketiga yang menerima fungsi pemeta kolom ke objek indeks. Migrasi dihasilkan oleh drizzle-kit generate yang membandingkan skema saat ini dengan keadaan basis data, sehingga setiap perubahan skema menghasilkan berkas SQL yang dapat dikomit dan direview.
Kesalahan tipe pada kolom tertangkap pada saat kompilasi TypeScript, bukan saat runtime, sebab tipe hasil kueri diturunkan dari deklarasi kolom. Pendekatan ini menjaga satu sumber kebenaran untuk skema dan tipe, sebab keduanya diturunkan dari definisi yang sama pada berkas TypeScript.
TypeORM: dekorator dan kelas entitas#
TypeORM menyatakan skema sebagai kelas TypeScript yang didekorasi dengan anotasi seperti @Entity, @Column, dan @PrimaryGeneratedColumn. Pendekatan ini mirip dengan ORM pada bahasa berbasis kelas seperti Doctrine pada PHP atau Hibernate pada Java, sehingga familiar bagi pengembang yang berasal dari ekosistem tersebut. TypeORM mendukung baik basis data relasional maupun MongoDB melalui adaptor yang berbeda.
// tidak-dijalankan: butuh typeorm dan server PostgreSQL
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
Index
} from 'typeorm';
@Entity()
@Index(['nama'])
export class Catatan {
@PrimaryGeneratedColumn()
id: number;
@Column()
nama: string;
@Column()
skor: number;
@CreateDateColumn({ name: 'dibuat_pada' })
dibuatPada: Date;
}Dekorator @Entity menandai kelas sebagai tabel, sedangkan dekorator kolom menentukan tipe dan opsi. @PrimaryGeneratedColumn() membuat kolom kunci utama dengan nilai otomatis, @Column() membuat kolom biasa, dan @CreateDateColumn mengisi kolom dengan waktu pembuatan baris secara otomatis. Indeks dideklarasikan pada level kelas dengan @Index, yang menerima larik nama properti sehingga indeks komposit dapat dibuat dengan satu dekorator.
Migrasi dihasilkan oleh typeorm migration:generate yang membandingkan kelas entitas dengan skema basis data, menghasilkan berkas migrasi yang berisi method up dan down untuk penerapan dan penarikan. Adaptor basis data pada TypeORM dipilih pada konfigurasi DataSource, sehingga satu proyek dapat berpindah antara PostgreSQL, MySQL, dan MongoDB dengan mengubah konfigurasi tanpa mengubah kelas entitas.
Memilih ORM#
Pemilihan ORM mempertimbangkan beberapa faktor. Prisma cocok untuk proyek yang menghargai pemisahan skema dari kode aplikasi dan menginginkan tooling migrasi yang matang, sebab berkas .prisma tetap dapat dibaca tanpa memahami TypeScript. Drizzle cocok untuk proyek yang menginginkan skema sebagai kode biasa tanpa langkah generate dan lebih dekat dengan SQL mentah. TypeORM cocok untuk tim yang familiar dengan pola dekorator kelas dan membutuhkan dukungan MongoDB dan relasional pada satu ORM.
Pemakaian ORM tidak selalu menjadi pilihan tepat. Pada skrip pendek satu kali, basis data kecil dengan skema tetap, atau proyek yang menuntut SQL tertulis tangan untuk alasan performa, driver mentah lebih sesuai. ORM menambah dependensi, langkah build, dan konvensi yang harus dipelajari tim, sehingga pada proyek kecil biaya tersebut tidak terbayar. Sebaliknya, pada proyek menengah hingga besar dengan banyak tabel dan kebutuhan migrasi yang terus berkembang, ORM biasanya menghemat waktu dan menjaga skema tetap konsisten.
Kesalahan umum#
Memakai ORM tanpa memahami SQL yang dihasilkan. ORM yang tampaknya sederhana pada kode dapat menerbitkan SQL yang membaca seluruh tabel atau melakukan N+1 query ketika relasi dimuat tanpa eager loading. Pelajari log SQL yang dihasilkan ORM pada lingkungan pengembangan, dan ukur kueri yang dicurigai dengan EXPLAIN ANALYZE pada PostgreSQL.
Mengabaikan migrasi dan mengandalkan sinkronisasi skema langsung. Fitur seperti prisma db push atau synchronize: true pada TypeORM memang sinkronisasi skema langsung tanpa migrasi, tetapi pendekatan ini berbahaya pada produksi sebab dapat menghapus kolom atau tabel tanpa peringatan. Gunakan migrasi yang ditulis dan dikomit ke kontrol versi, sehingga perubahan skema dapat direview dan dirollback.
Menganggap ORM menggantikan pengetahuan SQL. ORM mempercepat pekerjaan rutin, tetapi tetap memerlukan pemahaman tentang indeks, join, transaction isolation, dan batasan constraint. Kueri yang lambat sering tidak dapat diperbaiki tanpa membaca SQL yang dihasilkan, sehingga pengembang yang mengandalkan ORM tanpa dasar SQL akan kewalahan saat menghadapi kasus tersebut.
Ringkasan#
- ORM menyatakan skema basis data sebagai kode deklaratif, mengelola migrasi, dan menerbitkan tipe TypeScript, sedangkan query builder dan driver mentah berada di bawahnya.
- Prisma memakai berkas
.prismasebagai sumber kebenaran yang terpisah dari kode aplikasi, dengan client dan migrasi dihasilkan dari berkas ini. - Drizzle menyatakan skema langsung sebagai kode TypeScript tanpa langkah generate, sehingga skema dan kueri berada pada satu sumber.
- TypeORM memakai dekorator kelas seperti
@Entitydan@Column, familiar bagi pengembang dari ekosistem berbasis kelas dan mendukung relasional serta MongoDB. - ORM tepat dipakai pada proyek menengah hingga besar dengan skema yang berkembang, sedangkan skrip pendek atau kueri kompleks sering lebih sesuai dengan driver mentah.
Referensi#
- Prisma — Schema — Prisma
— Dokumentasi resmi berkas
schema.prismabeserta blokgenerator,datasource, danmodel. - Drizzle ORM — PostgreSQL column types — Drizzle Team — Daftar tipe kolom PostgreSQL pada Drizzle dan cara mendeklarasikan indeks.
- TypeORM — Entities — TypeORM
— Dokumentasi resmi entitas TypeORM beserta dekorator
@Entity,@Column, dan@Index.