Drizzle ORM 简介
Drizzle ORM 是与 Hono 最匹配的 TypeScript ORM——轻量、类型安全、支持边缘运行时(无 Node.js 依赖)。它采用"SQL-first"设计哲学:写 Drizzle 代码就像写 SQL,但享有完整的 TypeScript 类型推断。
| 特性 | Drizzle ORM | Prisma |
|---|---|---|
| 边缘运行时支持 | 完整支持 | 部分支持(需 Accelerate) |
| 包大小(运行时) | ~30KB | ~1.5MB+ |
| 查询风格 | SQL-like,直观 | 类对象,抽象更高 |
| Cloudflare D1 | 原生支持 | 需要第三方适配 |
| Schema 定义 | TypeScript 代码 | .prisma 文件 |
安装与配置
# 安装 Drizzle(SQLite / D1 适配器)
bun add drizzle-orm
bun add -D drizzle-kit
Schema 定义
// src/db/schema.ts
import { sqliteTable, text, integer, index } from 'drizzle-orm/sqlite-core'
import { sql } from 'drizzle-orm'
// 用户表
export const users = sqliteTable('users', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
name: text('name').notNull(),
email: text('email').notNull().unique(),
passwordHash: text('password_hash').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull().default(sql`(unixepoch())`),
}, (table) => ({
emailIdx: index('email_idx').on(table.email),
}))
// 文章表
export const posts = sqliteTable('posts', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
title: text('title').notNull(),
slug: text('slug').notNull().unique(),
content: text('content').notNull(),
published: integer('published', { mode: 'boolean' }).notNull().default(false),
authorId: text('author_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull().default(sql`(unixepoch())`),
updatedAt: integer('updated_at', { mode: 'timestamp' })
.notNull().default(sql`(unixepoch())`),
})
// 标签表
export const tags = sqliteTable('tags', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
name: text('name').notNull().unique(),
})
// 文章-标签关联表(多对多)
export const postTags = sqliteTable('post_tags', {
postId: text('post_id').notNull().references(() => posts.id, { onDelete: 'cascade' }),
tagId: text('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }),
})
Cloudflare D1 + Drizzle 集成
// src/db/index.ts — D1 适配器
import { drizzle } from 'drizzle-orm/d1'
import * as schema from './schema'
export function createDb(d1: D1Database) {
return drizzle(d1, { schema })
}
export type DB = ReturnType<typeof createDb>
// src/index.ts — 主入口
import { Hono } from 'hono'
import { createDb } from './db'
import postsRouter from './routes/posts'
type Env = { Bindings: { DB: D1Database } }
const app = new Hono<Env>()
// 将 db 实例注入到 context
app.use(async (c, next) => {
c.set('db', createDb(c.env.DB))
await next()
})
app.route('/api/posts', postsRouter)
export default app
博客系统:完整 API 实战
// src/routes/posts.ts — 文章 CRUD API
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
import { eq, desc, like, and, inArray } from 'drizzle-orm'
import { posts, tags, postTags } from '../db/schema'
const postsRouter = new Hono()
// 列表(分页 + 搜索 + 标签过滤)
postsRouter.get(
'/',
zValidator('query', z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().max(50).default(10),
search: z.string().optional(),
published: z.coerce.boolean().optional(),
})),
async (c) => {
const db = c.get('db')
const { page, limit, search, published } = c.req.valid('query')
const offset = (page - 1) * limit
const conditions = []
if (search) conditions.push(like(posts.title, `%${search}%`))
if (published !== undefined) conditions.push(eq(posts.published, published))
const results = await db
.select({
id: posts.id, title: posts.title, slug: posts.slug,
published: posts.published, createdAt: posts.createdAt,
})
.from(posts)
.where(and(...conditions))
.orderBy(desc(posts.createdAt))
.limit(limit)
.offset(offset)
return c.json({ posts: results, page, limit })
}
)
// 详情(含标签)
postsRouter.get('/:id', async (c) => {
const db = c.get('db')
const id = c.req.param('id')
const [post] = await db.select().from(posts).where(eq(posts.id, id))
if (!post) return c.json({ error: 'Post not found' }, 404)
// 查询关联标签
const postTagsResult = await db
.select({ name: tags.name })
.from(postTags)
.innerJoin(tags, eq(postTags.tagId, tags.id))
.where(eq(postTags.postId, id))
return c.json({ ...post, tags: postTagsResult.map(t => t.name) })
})
// 创建文章
postsRouter.post(
'/',
zValidator('json', z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
published: z.boolean().default(false),
tagNames: z.array(z.string()).default([]),
})),
async (c) => {
const db = c.get('db')
const { title, content, published, tagNames } = c.req.valid('json')
const authorId = c.get('userId') ?? 'anonymous'
// 生成 slug
const slug = title.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-')
// 插入文章
const [post] = await db.insert(posts).values({ title, slug, content, published, authorId }).returning()
// 处理标签(upsert)
if (tagNames.length > 0) {
const tagResults = await Promise.all(
tagNames.map(name =>
db.insert(tags).values({ name }).onConflictDoUpdate({
target: tags.name, set: { name }
}).returning()
)
)
await db.insert(postTags).values(
tagResults.flat().map(tag => ({ postId: post.id, tagId: tag.id }))
)
}
return c.json({ ...post, tags: tagNames }, 201)
}
)
export default postsRouter
Turso:全球分布式 SQLite
Turso 是基于 libSQL(SQLite 分支)的全球分布式数据库,可以在全球多个区域创建副本,与 Drizzle 完美集成:
# 安装 Turso 客户端
bun add @libsql/client drizzle-orm
// src/db/turso.ts
import { createClient } from '@libsql/client'
import { drizzle } from 'drizzle-orm/libsql'
import * as schema from './schema'
export function createTursoDb(url: string, authToken: string) {
const client = createClient({ url, authToken })
return drizzle(client, { schema })
}
// 使用(Node.js / Bun 环境)
const db = createTursoDb(
process.env.TURSO_DATABASE_URL!,
process.env.TURSO_AUTH_TOKEN!,
)
连接池策略:在 Edge 环境(Cloudflare Workers / Vercel Edge)中,每次请求可能由不同的 Isolate 处理,无法维持持久连接池。应使用 HTTP-based 客户端(如 Turso libSQL HTTP、Neon HTTP)而非 TCP 连接池。在传统服务器(Node.js / Bun)上,可以使用连接池(Prisma 默认 5 个连接)。