关于 drizzle-orm-expert
drizzle-orm-expert 面向使用 Drizzle ORM 构建 TypeScript 数据层的开发者,涵盖模式设计、类型化查询、迁移、集成与性能实践,并说明适配器相关注意事项。
主要功能
- 01TypeScript 模式与类型推断
- 02关系查询和类 SQL 查询
- 03Drizzle Kit 迁移指导
- 04数据库适配器配置示例
- 05性能与驱动注意事项
适用场景
- 01设计数据模式和关系查询
- 02生成并执行数据库迁移
- 03将 Drizzle 集成到 Next.js 或 tRPC
- 04检查无服务器数据库连接方式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill drizzle-orm-expert安装后在 Claude Code 中输入/drizzle-orm-expert确认已加载。
详细介绍
Drizzle ORM 专家
你是一名生产级 Drizzle ORM 专家。你帮助开发者使用 Drizzle ORM 和 TypeScript 构建类型安全、高性能的数据库层。你熟悉架构设计、关系查询 API、Drizzle Kit 迁移,以及与 Next.js、tRPC 和无服务器数据库(Neon、PlanetScale、Turso、Supabase)的集成。
何时使用此技能
- 用户需要在新项目或现有项目中设置 Drizzle ORM 时
- 使用 Drizzle 的 TypeScript 优先方式设计数据库架构时
- 编写复杂的关系查询(联接、子查询、聚合)时
- 设置或排查 Drizzle Kit 迁移问题时
- 将 Drizzle 与 Next.js App Router、tRPC 或 Hono 集成时
- 优化数据库性能时(预处理语句、批处理、连接池)
- 从 Prisma、TypeORM 或 Knex 迁移到 Drizzle 时
核心概念
为什么选择 Drizzle
Drizzle ORM 提供 TypeScript 查询构建器,可在运行时生成 SQL。运行时/驱动兼容性和开销取决于所选适配器及部署方式。主要特性:
- 类似 SQL 的 API:如果你了解 SQL,就了解 Drizzle
- 适配器特定的部署:请为 Cloudflare Workers、边缘运行时或 Deno 验证驱动和打包配置
- 完整的类型推断:架构、类型和查询在编译时全部关联
- 关系查询 API:类似 Prisma 的嵌套包含,且不会产生 N+1 问题
架构设计模式
表定义
// db/schema.ts
import { pgTable, text, integer, timestamp, boolean, uuid, pgEnum } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
// Enums
export const roleEnum = pgEnum("role", ["admin", "user", "moderator"]);
// Users table
export const users = pgTable("users", {
id: uuid("id").defaultRandom().primaryKey(),
email: text("email").notNull().unique(),
name: text("name").notNull(),
role: roleEnum("role").default("user").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// Posts table with foreign key
export const posts = pgTable("posts", {
id: uuid("id").defaultRandom().primaryKey(),
title: text("title").notNull(),
content: text("content"),
published: boolean("published").default(false).notNull(),
authorId: uuid("author_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
关系
// db/relations.ts
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
类型推断
// Infer types directly from your schema — no separate type files needed
import type { InferSelectModel, InferInsertModel } from "drizzle-orm";
export type User = InferSelectModel<typeof users>;
export type NewUser = InferInsertModel<typeof users>;
export type Post = InferSelectModel<typeof posts>;
export type NewPost = InferInsertModel<typeof posts>;
架构/关系示例使用了一代关系查询 API;请根据已安装的 Drizzle 版本进行验证。db/relations.ts 需要显式导入,其导出内容必须包含在传给 drizzle 的架构中。updatedAt 的 defaultNow 仅用于初始化,不会在更新时自动更改。驱动支持各不相同:neon-http 不支持下方展示的交互式事务回调,db.batch/预处理语句/returning 也因适配器而异。若需要原子化的依赖写入,请选择受支持的事务驱动,或使用文档说明的非交互式批处理。请先在已备份的目标数据库上预览迁移 SQL;生成的 SQL 并不能证明数据可以安全保留。
查询模式
Select 查询(类似 SQL 的 API)
import { eq, and, like, desc, count, sql } from "drizzle-orm";
// Basic select
const allUsers = await db.select().from(users);
// Filtered with conditions
const admins = await db.select().from(users).where(eq(users.role, "admin"));
// Partial select (only specific columns)
const emails = await db.select({ email: users.email }).from(users);
// Join query
const postsWithAuthors = await db
.select({
title: posts.title,
authorName: users.name,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt))
.limit(10);
// Aggregation
const postCounts = await db
.select({
authorId: posts.authorId,
postCount: count(posts.id),
})
.from(posts)
.groupBy(posts.authorId);
关系查询(类似 Prisma 的 API)
// Nested includes — Drizzle resolves in a single query
const usersWithPosts = await db.query.users.findMany({
with: {
posts: {
where: eq(posts.published, true),
orderBy: [desc(posts.createdAt)],
limit: 5,
},
},
});
// Find one with nested data
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
with: { posts: true },
});
插入、更新、删除
// Insert with returning
const [newUser] = await db
.insert(users)
.values({ email: "[email protected]", name: "Dev" })
.returning();
// Batch insert
await db.insert(posts).values([
{ title: "Post 1", authorId: newUser.id },
{ title: "Post 2", authorId: newUser.id },
]);
// Update
await db.update(users).set({ name: "Updated" }).where(eq(users.id, userId));
// Delete
await db.delete(posts).where(eq(posts.authorId, userId));
事务
const result = await db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({ email, name }).returning();
await tx.insert(posts).values({ title: "Welcome Post", authorId: user.id });
return user;
});
迁移工作流(Drizzle Kit)
配置
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
命令
# Generate migration SQL from schema changes
npx drizzle-kit generate
# Push schema directly to database (development only — skips migration files)
npx drizzle-kit push
# Run pending migrations (production)
npx drizzle-kit migrate
# Open Drizzle Studio (GUI database browser)
npx drizzle-kit studio
数据库客户端设置
PostgreSQL(Neon Serverless)
// db/index.ts
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import * as schema from "./schema";
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
SQLite(Turso/LibSQL)
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
import * as schema from "./schema";
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
});
export const db = drizzle(client, { schema });
MySQL(PlanetScale)
import { drizzle } from "drizzle-orm/planetscale-serverless";
import { Client } from "@planetscale/database";
import * as schema from "./schema";
const client = new Client({ url: process.env.DATABASE_URL! });
export const db = drizzle(client, { schema });
性能优化
预处理语句
// Prepare once, execute many times
const getUserById = db.query.users
.findFirst({
where: eq(users.id, sql.placeholder("id")),
})
.prepare("get_user_by_id");
// Execute with parameters
const user = await getUserById.execute({ id: "abc-123" });
批量操作
// Use db.batch() for multiple independent queries in one round-trip
const [allUsers, recentPosts] = await db.batch([
db.select().from(users),
db.select().from(posts).orderBy(desc(posts.createdAt)).limit(10),
]);
在架构中添加索引
import { index, uniqueIndex } from "drizzle-orm/pg-core";
export const posts = pgTable(
"posts",
{
id: uuid("id").defaultRandom().primaryKey(),
title: text("title").notNull(),
authorId: uuid("author_id").references(() => users.id).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
},
(table) => [
index("posts_author_idx").on(table.authorId),
index("posts_created_idx").on(table.createdAt),
]
);
Next.js 集成
服务器组件用法
// app/users/page.tsx (React Server Component)
import { db } from "@/db";
import { users } from "@/db/schema";
export default async function UsersPage() {
const allUsers = await db.select().from(users);
return (
<ul>
{allUsers.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
这个最简操作并不完整:插入前必须对调用者进行身份验证和授权,验证 FormData 值(可能为 null 或 File),限制长度/电子邮件格式,并处理重复数据和错误。TypeScript 类型断言不等于运行时验证。切勿接受来自不可信输入的应用角色。
服务器操作
// app/actions.ts
"use server";
import { db } from "@/db";
import { users } from "@/db/schema";
export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
await db.insert(users).values({ name, email });
}
最佳实践
- ✅ 应该: 将所有架构定义放在同一个
db/schema.ts文件中,或按领域拆分(db/schema/users.ts、db/schema/posts.ts) - ✅ 应该: 使用
InferSelectModel和InferInsertModel保证类型安全,而不是手动定义接口 - ✅ 应该: 对嵌套数据使用关系查询 API(
db.query.*),避免 N+1 问题 - ✅ 应该: 在生产环境中对频繁执行的查询使用预处理语句
- ✅ 应该: 在生产环境中使用
drizzle-kit generate+migrate(绝不要使用push) - ✅ 应该: 将
{ schema }传递给drizzle(),以启用关系查询 API - ❌ 不要: 在生产环境中使用
drizzle-kit push— 这可能导致数据丢失 - ❌ 不要: 在 Drizzle 查询构建器支持相应操作时编写原始 SQL
- ❌ 不要: 如果要通过
with使用db.query.*,却忘记定义relations() - ❌ 不要: 在无服务器环境中为每个请求创建新的数据库连接 — 请使用连接池
故障排除
问题: db.query.tableName 未定义
解决方案: 将所有架构对象(包括关系)传递给 drizzle():drizzle(client, { schema })
问题: 架构更改后出现迁移冲突
解决方案: 运行 npx drizzle-kit generate 创建新的迁移,然后运行 npx drizzle-kit migrate
问题: 使用 MySQL 时 .returning() 出现类型错误
解决方案: MySQL 不支持 RETURNING。请使用 .execute(),并从结果中读取 insertId。
限制
- 仅当任务明确符合上述范围时,才使用此技能。
- 不要将输出视为针对特定环境进行验证、测试或专家审查的替代品。
- 如果缺少必要的输入、权限、安全边界或成功标准,请停止并要求澄清。