Drizzle ORM: Advanced Queries dan Best Practices
Query kompleks, performance, dan pattern production-ready
Tujuan Pembelajaran
- Bisa menulis query kompleks dengan Drizzle
- Mengerti transaction dan batch queries
- Mengerti pattern repository dan service layer
Analogi
Advanced Patterns:
├── Transactions (db.transaction)
├── Batch inserts (db.insertAll)
├── Subqueries
├── CTE (Common Table Expressions)
└── Repository Pattern
Production code membutuhkan pattern yang terstruktur dan query yang efisien
Penjelasan Konsep
Setelah menguasai CRUD dasar dan migrations, saatnya mengeksplor teknik advanced yang dibutuhkan aplikasi production.
Transactions
Transaction memastikan beberapa operasi database berhasil SEMUA atau GAGAL SEMUA — tidak ada setengah-setengah.
Ini penting untuk operasi finansial, inventory, atau data yang saling bergantung.
await db.transaction(async (tx) => {
// Kurangi stok
await tx.update(products)
.set({ stock: sql`${products.stock} - 1` })
.where(eq(products.id, productId));
// Buat order
const order = await tx.insert(orders).values({ productId, userId }).returning();
// Kalau salah satu gagal, semua di-rollback
});
Raw SQL Fallback
Untuk query yang terlalu kompleks untuk query builder, gunakan raw SQL:
import { sql } from 'drizzle-orm';
const result = await db.run(sql`
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, 0 as depth
FROM categories WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.depth + 1
FROM categories c
JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree
`);
Notice: sql tagged template literal — tetap type-safe untuk parameters!
Batch Operations
Untuk insert/update banyak data sekaligus:
// Batch insert (lebih efisien daripada insert satu-satu)
await db.insert(users).values([
{ name: 'User 1',
Email: 'u1@mail.com' },
{ name: 'User 2', email: 'u2@mail.com' },
// ... 1000 users
]);
Repository Pattern
Untuk aplikasi besar, gunakan repository pattern untuk memisahkan data access logic:
// repositories/UserRepository.ts
export class UserRepository {
async findById(id: number) {
const [user] = await db.select().from(users).where(eq(users.id, id));
return user ?? null;
}
async findByEmail(email: string) {
const [user] = await db.select().from(users).where(eq(users.email, email));
return user ?? null;
}
async create(data: NewUser) {
const [user] = await db.insert(users).values(data).returning();
return user;
}
}
Prepared Statements
Drizzle secara otomatis menggunakan prepared statements untuk keamanan (mencegah SQL injection) dan performa (query plan di-cache oleh database).
Indexing
Tambahkan index di schema untuk query yang sering dijalankan:
export const users = sqliteTable('users', {
id: integer('id').primaryKey(),
email: text('email').notNull().unique(),
}, (table) => [
index('email_idx').on(table.email),
]); Contoh Kode
// Repository pattern implementation
import { eq, like, and, desc } from 'drizzle-orm';
import { db } from './db';
import { users, type User, type NewUser } from './schema';
export class UserRepository {
async findAll(options: { search?: string; role?: string; limit?: number; offset?: number } = {}) {
const { search, role, limit = 50, offset = 0 } = options;
const conditions = [];
if (search) conditions.push(like(users.name, `%${search}%`));
if (role) conditions.push(eq(users.role, role));
return db.select().from(users)
.where(conditions.length ? and(...conditions) : undefined)
.orderBy(desc(users.createdAt))
.limit(limit)
.offset(offset);
}
async findById(id: number): Promise<User | null> {
const [user] = await db.select().from(users).where(eq(users.id, id));
return user ?? null;
}
async findByEmail(email: string): Promise<User | null> {
const [user] = await db.select().from(users).where(eq(users.email, email));
return user ?? null;
}
async create(data: NewUser): Promise<User> {
const [user] = await db.insert(users).values(data).returning();
return user;
}
async update(id: number, data: Partial<NewUser>): Promise<User | null> {
const [user] = await db.update(users).set(data).where(eq(users.id, id)).returning();
return user ?? null;
}
async delete(id: number): Promise<void> {
await db.delete(users).where(eq(users.id, id));
}
async exists(email: string): Promise<boolean> {
const result = await db.select({ count: count() }).from(users).where(eq(users.email, email));
return result[0].count > 0;
}
}
// Usage in service layer
const userRepo = new UserRepository();
const user = await userRepo.findById(1);
if (!user) throw new HTTPException(404, { message: 'User not found' });Penjelasan Kode
Prompt AI
Implementasikan Unit of Work pattern dengan Drizzle transactions. Semua operasi dalam satu request menggunakan transaction yang sama.
Pertanyaan Reflektif
Tambahkan query logging untuk development. Catat setiap query yang dijalankan beserta execution time-nya.