blog.dopana

Back

Drizzle ORM là TypeScript ORM “SQL-like” — bạn viết SQL, nó suy ra type.

Khô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#

PrismaDrizzle
SchemaSDL riêng (.prisma)TypeScript code
Codegenprisma generateKhông cần
Kích thước~30MB~500KB
Edge support⚠️ Hạn chế✅ Native
SQL-like query
Raw SQL⚠️✅ Dễ
Migrationprisma migratedrizzle-kit push/migrate
Joininclude/selectleftJoin/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(),
})
typescript

PostgreSQL#

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'),
})
typescript

MySQL#

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(),
})
typescript

Query#

SELECT#

INSERT#

// 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' },
])
typescript

UPDATE#

await db.update(users)
  .set({ name: 'Alice Updated' })
  .where(eq(users.id, 1))
typescript

DELETE#

await db.delete(users)
  .where(eq(users.id, 1))
typescript

Relations & Join#

Query 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 }[]
typescript

Connection 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)
  }
}
typescript

Neon (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)
typescript

Bun + SQLite#

import { drizzle } from 'drizzle-orm/bun-sqlite'
import { Database } from 'bun:sqlite'

const sqlite = new Database('blog.db')
const db = drizzle(sqlite)
typescript

Migration#

Push (dev)#

npx drizzle-kit push
# Sync schema → DB trực tiếp
bash

Generate migration#

npx drizzle-kit generate
# Tạo SQL migration file
bash
-- 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`);
sql

Apply migration#

npx drizzle-kit migrate
bash

Prepared 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' })
typescript

Raw 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'`)
typescript

Khi 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))
typescript

Prisma#

const result = await prisma.post.findMany({
  where: { published: true },
  select: {
    id: true,
    title: true,
    author: { select: { name: true } },
  },
  orderBy: { createdAt: 'desc' },
})
typescript

Drizzle 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.

PrismaDrizzle
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-ready
typescript

Tài liệu tham khảo#