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 这种"轻量拼装"的路线?欢迎聊聊你的选择。

更多推荐