Drizzle ORM:TypeScript 世界里一把不带锁的刀
2026 年的 TypeScript 后端生态已经足够成熟,但 ORM 这个赛道始终让人有点纠结。Prisma 功能强大却笨重,TypeORM 设计老旧,Knex 太底层。就是在这个夹缝里,Drizzle ORM 杀了出来,用一种"我什么都不替你决定"的姿态,快速收割了大量开发者的好感。
Drizzle 的口号是"SQL-like ORM",本质上它想解决一个老问题:ORM 总想帮你屏蔽 SQL,但屏蔽得太狠,反而让你失控。Drizzle 反其道而行,让你写出来的代码几乎就是 SQL 的镜像,同时保留 TypeScript 的类型安全。
说实话,我第一次看到 Drizzle 的 API 时,感觉它不像一个 2020 年代的产物——没有花哨的装饰器,没有厚重的 Schema DSL,甚至没有 CLI 脚手架。但正是这种极简,让人上手十分钟就把一个项目从 Prisma 迁了过去,整个过程比想象中顺畅得多。
优点:轻、快、透明
零抽象税。Drizzle 在运行时几乎不做额外计算,生成的 SQL 和你手写的几乎一模一样。它的查询构建器在编译期就把类型推导完成了,运行时只是拼接字符串。这意味着冷启动极快,边缘函数和 Serverless 场景下优势明显。
SQL-first 哲学。你不需要学一套新的查询语言。db.select().from(users).where(eq(users.id, 1)) 和 SELECT * FROM users WHERE id = 1 之间几乎没有认知转换成本。对于从 SQL 转过来的开发者,这是降维打击式的友好。
多运行时支持。Drizzle 不绑定任何特定平台。你可以用 node-postgres、mysql2、better-sqlite3、bun:sqlite,甚至 Cloudflare D1、PlanetScale、Neon、Turso——全部统一 API。切换数据库驱动只需要改连接那一行。
迁移体验清爽。Drizzle Kit 生成的是纯 SQL 迁移文件,不搞黑盒 DSL。你可以直接读、改、review 迁移内容,团队协作时直接当普通 SQL 文件走 Code Review 流程。这一点在需要 DBA 介入的生产环境中几乎是个刚需。
类型安全但不绑架你。Drizzle 的类型系统基于 TypeScript 的模板字面量类型,能够推导出 JOIN 后的结果形状、WHERE 条件中的列类型、甚至聚合函数的返回值类型。但它不强制你使用这些推导——你可以随时降级到 sql 模板字面量写原生查询,类型检查依然在线。
使用场景
Serverless / Edge:Vercel Edge Functions、Cloudflare Workers、Deno Deploy 这些环境对启动时间极其敏感。Drizzle 的包体小、无初始化开销、驱动无关设计,让它在冷启动场景下几乎是无敌的存在。搭配 PlanetScale 或 Neon 的 HTTP 驱动,连 TCP 连接都不用建立。
快速迭代的原型项目:不需要搭建复杂的 Schema 管理流程,一个 drizzle.config.ts 加几个 Schema 文件就能起步。改表结构就是改 TypeScript 类型定义,然后跑 drizzle-kit push,一步到位。
需要精细控制 SQL 的生产项目:如果你的团队有 DBA,或者你需要做复杂的报表查询、窗口函数、CTE、递归查询——Drizzle 不会成为你的绊脚石。它的 sql 操作符让你随时切入原生 SQL 模式,同时保留类型推导。
多数据库适配的中间件:SaaS 产品需要同时支持 PostgreSQL 和 MySQL?Drizzle 的 Schema 定义统一,查询 API 统一,只有连接器不同。理论上你可以通过切换驱动实现全数据库覆盖,而业务代码一行不变。
具体使用方式
先从零搭建一个 Drizzle + PostgreSQL 项目:
npm i drizzle-orm postgres npm i -D drizzle-kit
定义 Schema。Drizzle 的 Schema 文件就是普通的 TypeScript:
// db/schema.ts
import { pgTable, serial, varchar, text, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name", { length: 100 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
bio: text("bio"),
createdAt: timestamp("created_at").defaultNow(),
});
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
userId: serial("user_id").references(() => users.id),
title: varchar("title", { length: 255 }).notNull(),
content: text("content").notNull(),
createdAt: timestamp("created_at").defaultNow(),
});
建立连接并执行查询:
// db/index.ts
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, { schema });
// 增删改查示例
import { db } from "./db";
import { users, posts } from "./db/schema";
import { eq, and, desc } from "drizzle-orm";
// 插入
const [newUser] = await db.insert(users).values({
name: "Marvis",
email: "marvis@example.com",
}).returning();
// 联表查询——返回类型自动推导
const result = await db.select({
userName: users.name,
postTitle: posts.title,
}).from(users)
.leftJoin(posts, eq(users.id, posts.userId))
.where(eq(users.id, 1))
.orderBy(desc(posts.createdAt));
// 事务
await db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({...}).returning();
await tx.insert(posts).values({ userId: user.id, ...});
});
生成迁移:
npx drizzle-kit generate npx drizzle-kit push
生成的迁移文件长这样:
-- 0000_plain_jubilee.sql
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY,
"name" varchar(100) NOT NULL,
"email" varchar(255) NOT NULL UNIQUE,
"bio" text,
"created_at" timestamp DEFAULT now()
);
整个过程没有魔法,每一步都可审计、可回滚、可在 PR 里逐行审查。
写到这里我突然意识到,Drizzle 的流行可能不只是技术选型的问题。它背后是一种开发哲学的回归——工具应该服务于人,而不是反过来。当 ORM 试图替你"简化"一切时,它也在悄悄把你和数据库的真实行为隔离开来。Drizzle 做的事情其实很简单:给你 TypeScript 的类型安全,但不替你藏起 SQL。这个平衡点踩得刚刚好。
你觉得,2026 年的后端开发,是继续拥抱全栈框架的"一站式黑盒",还是该回归到 Drizzle 这种"轻量拼装"的路线?欢迎聊聊你的选择。
更多推荐
所有评论(0)