Drizzle ORM — SQL Cho TypeScript Type-Safe
Drizzle ORM là TypeScript ORM kiểu SQL-first, nhẹ hơn Prisma, không codegen, chạy trên edge. Schema → query type-safe trực tiếp.
Drizzle ORM là TypeScript ORM “SQL-like” — bạn viết SQL, nó suy ra type.
import { drizzle } from 'drizzle-orm/better-sqlite3'
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
import Database from 'better-sqlite3'
const db = drizzle(new Database('blog.db'))
const users = sqliteTable('users', {
id: integer().primaryKey({ autoIncrement: true }),
name: text().notNull(),
email: text().notNull().unique(),
})
// Query — type-safe, SQL-like syntax
const result = await db.select()
.from(users)
.where(eq(users.email, 'alice@example.com'))
// result: { id: number; name: string; email: string }[]typescriptKhông Prisma schema riêng, không codegen, không chạy CLI để generate type. Schema = code → type suy ra tự động.
Drizzle vs Prisma#
| Prisma | Drizzle | |
|---|---|---|
| Schema | SDL riêng (.prisma) | TypeScript code |
| Codegen | prisma generate | Không cần |
| Kích thước | ~30MB | ~500KB |
| Edge support | ⚠️ Hạn chế | ✅ Native |
| SQL-like query | ❌ | ✅ |
| Raw SQL | ⚠️ | ✅ Dễ |
| Migration | prisma migrate | drizzle-kit push/migrate |
| Join | include/select | leftJoin/innerJoin |
Drizzle nhẹ hơn, TypeScript-native hơn, và chạy được trên edge (Cloudflare Workers, Deno, Bun).
Schema#
SQLite#
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core'
export const posts = sqliteTable('posts', {
id: integer().primaryKey({ autoIncrement: true }),
title: text().notNull(),
slug: text().notNull().unique(),
content: text().notNull(),
publishedAt: integer({ mode: 'timestamp' }).notNull(),
views: integer().default(0),
rating: real(),
})typescriptPostgreSQL#
import { pgTable, text, integer, timestamp, serial } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial().primaryKey(),
name: text().notNull(),
email: text().notNull().unique(),
createdAt: timestamp().defaultNow().notNull(),
role: text().$type<'admin' | 'user' | 'moderator'>().default('user'),
})typescriptMySQL#
import { mysqlTable, int, text, varchar } from 'drizzle-orm/mysql-core'
export const products = mysqlTable('products', {
id: int().primaryKey().autoincrement(),
name: varchar({ length: 255 }).notNull(),
price: int().notNull(),
description: text(),
})typescriptQuery#
SELECT#
// Tất cả columns
const all = await db.select().from(users)
// Column cụ thể
const names = await db.select({ name: users.name }).from(users)
// WHERE
const active = await db.select()
.from(users)
.where(eq(users.status, 'active'))
// WHERE với nhiều điều kiện
const result = await db.select()
.from(users)
.where(and(
eq(users.status, 'active'),
gte(users.age, 18),
like(users.name, '%Alice%'),
))
// ORDER BY + LIMIT + OFFSET
const page = await db.select()
.from(users)
.orderBy(desc(users.createdAt))
.limit(10)
.offset(0)typescriptINSERT#
// Single
const [user] = await db.insert(users).values({
name: 'Alice',
email: 'alice@example.com',
}).returning()
// Bulk
await db.insert(users).values([
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' },
])typescriptUPDATE#
await db.update(users)
.set({ name: 'Alice Updated' })
.where(eq(users.id, 1))typescriptDELETE#
await db.delete(users)
.where(eq(users.id, 1))typescriptRelations & Join#
import { relations } from 'drizzle-orm'
export const users = sqliteTable('users', {
id: integer().primaryKey({ autoIncrement: true }),
name: text().notNull(),
})
export const posts = sqliteTable('posts', {
id: integer().primaryKey({ autoIncrement: true }),
title: text().notNull(),
authorId: integer().notNull().references(() => users.id),
})
// Định nghĩa relations
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}))typescriptQuery với joins#
// JOIN thủ công
const result = await db.select()
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id))
// result: { posts: Post; users: User | null }[]
// JOIN với Drizzle relations (cần db.query)
const result = await db.query.posts.findMany({
with: {
author: true,
},
})
// result: { id: number; title: string; authorId: number; author: User }[]typescriptConnection cho Edge#
Cloudflare Workers (D1)#
import { drizzle } from 'drizzle-orm/d1'
export default {
async fetch(request, env) {
const db = drizzle(env.DB) // env.DB = D1 binding
const posts = await db.select().from(users)
return Response.json(posts)
}
}typescriptNeon (Serverless PostgreSQL)#
import { drizzle } from 'drizzle-orm/neon-http'
import { neon } from '@neondatabase/serverless'
const sql = neon(process.env.DATABASE_URL!)
const db = drizzle(sql)
const result = await db.select().from(users)typescriptBun + SQLite#
import { drizzle } from 'drizzle-orm/bun-sqlite'
import { Database } from 'bun:sqlite'
const sqlite = new Database('blog.db')
const db = drizzle(sqlite)typescriptMigration#
Push (dev)#
npx drizzle-kit push
# Sync schema → DB trực tiếpbashGenerate migration#
npx drizzle-kit generate
# Tạo SQL migration filebash-- drizzle/migrations/0000_xxxx.sql
CREATE TABLE `users` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`email` text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);sqlApply migration#
npx drizzle-kit migratebashPrepared Statements#
// Drizzle tự động prepared statements
const stmt = db.select()
.from(users)
.where(eq(users.email, sql.placeholder('email')))
.prepare('get_user_by_email')
// Dùng lại — chỉ parse 1 lần
const user1 = await stmt.execute({ email: 'alice@example.com' })
const user2 = await stmt.execute({ email: 'bob@example.com' })typescriptRaw SQL#
import { sql } from 'drizzle-orm'
// Query raw — vẫn type-safe với template
const users = await db.execute(
sql`SELECT * FROM users WHERE email = ${email}`
)
// SQL fragment
const activeUsers = await db.select()
.from(users)
.where(sql`${users.status} = 'active'`)typescriptKhi Nào Dùng Drizzle?#
✅ Nên dùng#
- Edge deployment — Cloudflare Workers, Deno Deploy
- Serverless — Neon, PlanetScale, Turso
- Dự án TypeScript — muốn type-safe mà không codegen
- SQL-first team — thích viết SQL, không muốn ORM che giấu
- Bundle size quan trọng — ~500KB vs Prisma ~30MB
❌ Không nên#
- Cần admin UI / dashboard — Prisma Studio tốt hơn
- Schema phức tạp, nhiều relation — Prisma relations API dễ hơn
- Dùng Prisma rồi, đang ổn — migration không đáng
- Cần GraphQL integration — Prisma + GraphQL ecosystem mạnh hơn
So Sánh Query#
Drizzle#
const result = await db.select({
id: posts.id,
title: posts.title,
authorName: users.name,
})
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt))typescriptPrisma#
const result = await prisma.post.findMany({
where: { published: true },
select: {
id: true,
title: true,
author: { select: { name: true } },
},
orderBy: { createdAt: 'desc' },
})typescriptDrizzle gần SQL hơn — bạn biết SQL là bạn biết Drizzle. Prisma abstraction cao hơn, nhưng khi cần raw SQL thì khó hơn.
Tổng Kết#
Drizzle chiếm chỗ riêng: TypeScript ORM nhẹ, SQL-like, edge-native.
| Prisma | Drizzle | |
|---|---|---|
| Bundle | ~30MB | ~500KB |
| Codegen | ✅ Bắt buộc | ✅ Không cần |
| Edge | ⚠️ Hạn chế | ✅ Workers, Deno, Bun |
| SQL feeling | ❌ | ✅ |
| Type safety | ✅ | ✅ |
| Migration | ✅ | ✅ |
| Raw SQL | ⚠️ | ✅ |
// Drizzle: schema → query, vài dòng là xong
const db = drizzle(env.DB)
const posts = await db.select().from(users).where(eq(users.active, true))
// ✅ Type-safe, không codegen, edge-readytypescript