为什么选择 Drizzle ORM?
在 TypeScript 全栈开发中,数据库访问层的选型一直是个痛点。Prisma 虽然流行,但运行时开销大、客户端体积臃肿;TypeORM 配置繁琐且类型推断不够智能;Kysely 虽然轻量,但缺乏 Schema 定义能力。
Drizzle ORM 填补了这个空白——它是一个 TypeScript-first 的 ORM,核心理念是:
- 零运行时开销:生成的 SQL 就是你手写的 SQL,没有黑魔法
- 端到端类型安全:从 Schema 定义到查询结果,全链路类型推断
- SQL-like 语法:如果你熟悉 SQL,Drizzle 的 API 会让你感到亲切
- 轻量高效:核心包不到 50KB,适合 Serverless 和边缘计算场景
本文将从零搭建一个完整的 Drizzle ORM 项目,涵盖 Schema 定义、数据库迁移、CRUD 操作和 Next.js 集成。
项目初始化
首先创建项目并安装依赖:
1
2
3
4
5
6
7
8
9
10
|
# 创建项目
mkdir drizzle-demo && cd drizzle-demo
npm init -y
# 安装核心依赖
npm install drizzle-orm better-sqlite3
npm install -D drizzle-kit typescript @types/better-sqlite3 tsx
# 初始化 TypeScript
npx tsc --init
|
配置 tsconfig.json:
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
|
定义 Schema
Drizzle 的 Schema 定义非常直观,直接映射数据库表结构。创建 src/schema.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core";
import { sql } from "drizzle-orm";
// 用户表
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
email: text("email").notNull().unique(),
avatar: text("avatar"),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
});
// 文章表
export const posts = sqliteTable("posts", {
id: integer("id").primaryKey({ autoIncrement: true }),
title: text("title").notNull(),
content: text("content").notNull(),
slug: text("slug").notNull().unique(),
published: integer("published", { mode: "boolean" }).default(false),
authorId: integer("author_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
});
// 标签表(多对多关系)
export const tags = sqliteTable("tags", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull().unique(),
});
export const postTags = sqliteTable("post_tags", {
postId: integer("post_id")
.notNull()
.references(() => posts.id, { onDelete: "cascade" }),
tagId: integer("tag_id")
.notNull()
.references(() => tags.id, { onDelete: "cascade" }),
});
|
可以看到,Schema 定义与 SQL 建表语句几乎一一对应,但完全由 TypeScript 类型驱动。
配置 Drizzle Kit
Drizzle Kit 是官方的迁移和开发工具。创建 drizzle.config.ts:
1
2
3
4
5
6
7
8
9
10
|
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/schema.ts",
out: "./drizzle",
dialect: "sqlite",
dbCredentials: {
url: "./local.db",
},
});
|
在 package.json 中添加脚本:
1
2
3
4
5
6
7
8
9
|
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"dev": "tsx watch src/index.ts"
}
}
|
数据库连接与初始化
创建 src/db.ts:
1
2
3
4
5
6
|
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import * as schema from "./schema";
const sqlite = new Database("./local.db");
export const db = drizzle(sqlite, { schema });
|
运行迁移创建表结构:
1
2
3
4
5
|
# 生成迁移文件
npm run db:generate
# 直接推送到数据库(开发环境推荐)
npm run db:push
|
CRUD 操作实战
创建 src/index.ts,演示完整的 CRUD 操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
import { db } from "./db";
import { users, posts, tags, postTags } from "./schema";
import { eq, desc, like, and, sql } from "drizzle-orm";
async function main() {
// ========== 创建数据 ==========
// 插入用户
const [user] = await db
.insert(users)
.values({
name: "张三",
email: "[email protected]",
})
.returning(); // 返回插入的记录
console.log("创建用户:", user);
// 批量插入文章
const insertedPosts = await db
.insert(posts)
.values([
{
title: "Drizzle ORM 入门指南",
content: "Drizzle 是一个 TypeScript-first 的 ORM...",
slug: "drizzle-orm-guide",
published: true,
authorId: user.id,
},
{
title: "TypeScript 类型体操",
content: "深入理解 TypeScript 高级类型...",
slug: "typescript-type-gymnastics",
published: false,
authorId: user.id,
},
])
.returning();
console.log(`创建了 ${insertedPosts.length} 篇文章`);
// ========== 查询数据 ==========
// 查询所有已发布文章(带作者信息)
const publishedPosts = await db
.select({
id: posts.id,
title: posts.title,
slug: posts.slug,
authorName: users.name,
authorEmail: users.email,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt));
console.log("已发布文章:", publishedPosts);
// 条件查询:模糊搜索
const searchResults = await db
.select()
.from(posts)
.where(like(posts.title, "%TypeScript%"));
console.log("搜索结果:", searchResults);
// 聚合查询:统计每个用户的发布数量
const postCounts = await db
.select({
authorId: posts.authorId,
authorName: users.name,
count: sql<number>`count(*)`.as("post_count"),
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.groupBy(posts.authorId);
console.log("发布统计:", postCounts);
// ========== 更新数据 ==========
// 更新单条记录
await db
.update(posts)
.set({
published: true,
updatedAt: new Date(),
})
.where(eq(posts.slug, "typescript-type-gymnastics"));
// 批量更新
await db
.update(posts)
.set({ updatedAt: new Date() })
.where(and(eq(posts.published, true), eq(posts.authorId, user.id)));
// ========== 删除数据 ==========
// 删除单条记录
await db.delete(posts).where(eq(posts.slug, "drizzle-orm-guide"));
// 级联删除(用户删除后文章自动删除)
await db.delete(users).where(eq(users.id, user.id));
console.log("CRUD 操作完成!");
}
main().catch(console.error);
|
运行项目:
事务处理
Drizzle 支持两种事务模式,适合不同的使用场景:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import { db } from "./db";
import { users, posts } from "./schema";
// 方式一:回调事务(推荐)
await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values({ name: "李四", email: "[email protected]" })
.returning();
await tx.insert(posts).values({
title: "事务处理指南",
content: "Drizzle 支持强大的事务处理...",
slug: "transaction-guide",
authorId: user.id,
});
// 如果抛出异常,整个事务会回滚
});
// 方式二:手动事务控制
const tx = db.transaction();
try {
await tx.insert(users).values({ name: "王五", email: "[email protected]" });
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}
|
Next.js 集成
在 Next.js App Router 中使用 Drizzle ORM 非常自然。创建 lib/db.ts:
1
2
3
4
5
6
|
import { drizzle } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import * as schema from "@/schema";
const sqlite = new Database(process.env.DATABASE_URL || "./local.db");
export const db = drizzle(sqlite, { schema });
|
API Route 示例 (app/api/posts/route.ts):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import { NextResponse } from "next/server";
import { db } from "@/lib/db";
import { posts } from "@/schema";
import { desc, eq } from "drizzle-orm";
export async function GET() {
const allPosts = await db
.select()
.from(posts)
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt));
return NextResponse.json(allPosts);
}
export async function POST(request: Request) {
const body = await request.json();
const [newPost] = await db
.insert(posts)
.values({
title: body.title,
content: body.content,
slug: body.slug,
authorId: body.authorId,
})
.returning();
return NextResponse.json(newPost, { status: 201 });
}
|
Server Component 示例 (app/posts/page.tsx):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import { db } from "@/lib/db";
import { posts, users } from "@/schema";
import { eq, desc } from "drizzle-orm";
export default async function PostsPage() {
// 在 Server Component 中直接查询数据库
const allPosts = await db
.select({
id: posts.id,
title: posts.title,
slug: posts.slug,
authorName: users.name,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt));
return (
<div>
<h1>博客文章</h1>
{allPosts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>作者: {post.authorName}</p>
</article>
))}
</div>
);
}
|
性能对比
Drizzle ORM 相比其他 ORM 的性能优势:
| 指标 |
Drizzle |
Prisma |
TypeORM |
| 冷启动时间 |
~5ms |
~200ms |
~150ms |
| 包体积 |
~50KB |
~5MB+ |
~2MB |
| 类型推断 |
编译时 |
运行时 |
运行时 |
| SQL 控制 |
完全 |
部分 |
部分 |
| 学习曲线 |
低(熟悉SQL) |
中 |
高 |
Drizzle 的冷启动时间特别适合 Serverless 和边缘计算场景,这也是它在 Vercel、Cloudflare Workers 社区迅速流行的原因。
最佳实践
- Schema 即文档:把 Schema 文件当作数据库文档,字段名、类型、约束一目了然
- 善用
returning():插入和更新时使用 returning() 获取操作结果,避免额外查询
- 合理使用事务:涉及多表操作时务必使用事务,保证数据一致性
- 利用 TypeScript 类型:Drizzle 会自动推断查询结果类型,充分利用 IDE 的类型提示
- 开发环境用
push,生产环境用 migrate:drizzle-kit push 适合快速迭代,migrate 适合正式环境的版本控制
总结
Drizzle ORM 代表了 TypeScript 数据库访问层的新方向:零运行时开销、端到端类型安全、SQL-like 语法。它特别适合:
- Next.js / Remix 等全栈框架:Server Component 中直接查询数据库
- Serverless / 边缘计算:冷启动时间极短
- 类型安全优先的团队:编译时捕获数据库错误
- 熟悉 SQL 的开发者:无需学习新的查询语言
如果你正在寻找一个轻量、高效、类型安全的 TypeScript ORM,Drizzle ORM 绝对值得一试。