Drizzle ORM + PostgreSQL + Hono 学习笔记
用 Drizzle ORM 操作 PostgreSQL 的完整知识点整理。涵盖 Docker 部署、Schema 设计、Migration 工作流、查询语法、常用细节。
目录
- 一、整体技术栈
- 二、Docker 跑 PostgreSQL
- 三、Drizzle ORM 基础
- 四、Schema 定义
- 五、Migration 工作流
- 六、查询语法
- 七、Drizzle Studio
- 八、关键细节与坑
- 九、完整最小项目结构
- 十、参考资源
一、整体技术栈
| 工具 | 作用 | 包名 |
|---|---|---|
| PostgreSQL 16 | 数据库 | (Docker 镜像 postgres:16) |
| Drizzle ORM | TS 友好的 ORM | drizzle-orm |
| postgres | Node 连 Postgres 的客户端 | postgres |
| drizzle-kit | 命令行工具,生成/执行 migration | drizzle-kit |
| Hono | HTTP 框架 | hono |
| Docker | 数据库容器化 | (装 Docker Desktop) |
安装命令:
csharp
pnpm add drizzle-orm postgres
pnpm add -D drizzle-kit
二、Docker 跑 PostgreSQL
1. 为什么用 Docker 而不是本机装
| 项 | 本机装 | Docker 跑 |
|---|---|---|
| 安装/卸载 | 麻烦,残留多 | 一行命令搞定 |
| 多版本共存 | 冲突频繁 | 容器隔离 |
| 团队同步 | 各装各的 | 共用 docker-compose.yml |
| 部署一致性 | dev/prod 不同环境 | 容器完全可复现 |
2. docker-compose.yml 模板
yaml
services:
postgres:
image: postgres:16
container_name: legal-agent-postgres
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: legal_agent
ports:
- '5432:5432' # 主机端口:容器端口
volumes:
- postgres_data:/var/lib/postgresql/data # 数据持久化
volumes:
postgres_data:
3. 常用 Docker 命令
bash
docker compose up -d # 后台启动
docker compose down # 停止并删容器(数据保留)
docker compose down -v # 停止且删数据(危险)
docker ps # 查看正在运行的容器
docker logs <container_name> # 查看容器日志
docker exec -it <container_name> psql -U postgres # 进入容器执行 psql
三、Drizzle ORM 基础
1. 核心特点
- Schema 即类型:写 schema 自动生成 TS 类型,不需要 codegen 步骤(对比 Prisma)
- 贴近 SQL:查询语法接近原生 SQL,学习成本低
- 零运行时开销:编译后没有抽象层
- 支持多数据库 :PostgreSQL / MySQL / SQLite,通过不同
core切换
2. 创建数据库连接
javascript
// src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
const connectionString = process.env.DATABASE_URL!
if (!connectionString) {
throw new Error('DATABASE_URL is not set')
}
const queryClient = postgres(connectionString)
export const db = drizzle(queryClient)
3. 连接字符串格式
bash
postgresql://username:password@host:port/database
postgresql://postgres:postgres@localhost:5432/legal_agent
四、Schema 定义
1. 基础写法
css
import {
pgTable,
uuid,
varchar,
text,
timestamp,
integer,
boolean,
} from 'drizzle-orm/pg-core'
export const chats = pgTable('chats', {
id: uuid('id').defaultRandom().primaryKey(),
title: varchar('title', { length: 200 }).notNull(),
systemPrompt: text('system_prompt'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
})
2. 常用字段类型(PostgreSQL)
| Drizzle 方法 | SQL 类型 | 用途 |
|---|---|---|
uuid() |
uuid | 唯一标识 |
varchar({ length }) |
varchar(N) | 短字符串(标题、名称) |
text() |
text | 长文本(无长度限制) |
integer() |
integer | 整数 |
bigint({ mode: 'number' }) |
bigint | 大整数 |
boolean() |
boolean | 布尔值 |
timestamp() |
timestamp | 时间戳 |
jsonb() |
jsonb | JSON 数据(可索引,推荐用) |
vector({ dimensions: 1536 }) |
vector(1536) | 向量(需要 pgvector) |
3. 常用修饰符
scss
.notNull() // 不能为空
.primaryKey() // 主键
.unique() // 唯一约束
.default(value) // 默认值
.defaultNow() // 默认当前时间
.defaultRandom() // 默认随机 UUID
.references(() => otherTable.id) // 外键
4. 类型推导
typescript
export type Chat = typeof chats.$inferSelect // 查询返回的类型
export type NewChat = typeof chats.$inferInsert // 插入时使用的类型
// 类型自动是:
// type Chat = {
// id: string
// title: string
// systemPrompt: string | null
// createdAt: Date
// updatedAt: Date
// }
关键认知 :$inferSelect 和 $inferInsert 不同------insert 时 defaultRandom() / defaultNow() 的字段是可选的。
五、Migration 工作流
1. 配置文件 drizzle.config.ts
typescript
import type { Config } from 'drizzle-kit'
export default {
schema: './src/db/schema.ts',
out: './src/db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
} satisfies Config
2. package.json Scripts
json
{
"scripts": {
"dev": "tsx watch --env-file=.env src/index.ts",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio",
"db:push": "drizzle-kit push"
}
}
--env-file=.env 是 Node 20+ 内置功能 ,无需 dotenv 包。
3. 标准工作流
sql
改 schema.ts → pnpm db:generate → 生成 migration SQL → pnpm db:migrate → 应用到数据库
4. generate vs push 的区别
| 命令 | 用途 | 场景 |
|---|---|---|
db:generate |
生成 SQL 文件,需要手动 migrate | 生产环境,有审计 |
db:push |
直接同步 schema 到数据库,不生成文件 | 开发环境快速迭代 |
实际项目:开发用 push,上线前 generate + migrate。
5. Migration 文件长什么样
sql
-- src/db/migrations/0000_xxx.sql
CREATE TABLE IF NOT EXISTS "chats" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"title" varchar(200) NOT NULL,
"system_prompt" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
这个文件要提交 Git------团队共享、生产部署、可追溯。
六、查询语法
1. SELECT 查询
csharp
import { db } from './db'
import { chats } from './db/schema'
import { eq, and, or, gt, lt, like, desc } from 'drizzle-orm'
// 查全部
const all = await db.select().from(chats)
// 条件查询
const filtered = await db
.select()
.from(chats)
.where(eq(chats.id, someId))
// 多条件
const complex = await db
.select()
.from(chats)
.where(
and(
eq(chats.userId, userId),
gt(chats.createdAt, new Date('2026-01-01'))
)
)
// 排序 + 分页
const paged = await db
.select()
.from(chats)
.orderBy(desc(chats.createdAt))
.limit(20)
.offset(0)
// 只取某些字段
const minimal = await db
.select({
id: chats.id,
title: chats.title,
})
.from(chats)
2. INSERT 插入
php
// 插入一条
const [newChat] = await db
.insert(chats)
.values({ title: 'Hello' })
.returning()
// 批量插入
const newChats = await db
.insert(chats)
.values([
{ title: 'A' },
{ title: 'B' },
])
.returning()
// 冲突处理
await db
.insert(chats)
.values({ id: 'xxx', title: 'New' })
.onConflictDoUpdate({
target: chats.id,
set: { title: 'New' },
})
3. UPDATE 更新
perl
await db
.update(chats)
.set({ title: 'Updated' })
.where(eq(chats.id, someId))
.returning()
4. DELETE 删除
perl
await db
.delete(chats)
.where(eq(chats.id, someId))
5. 查询操作符速查
| 操作符 | 含义 |
|---|---|
eq(col, val) |
= |
ne(col, val) |
!= |
gt(col, val) |
> |
gte(col, val) |
>= |
lt(col, val) |
< |
lte(col, val) |
<= |
like(col, '%xx%') |
LIKE |
ilike(col, '%xx%') |
LIKE(不区分大小写) |
inArray(col, [a, b]) |
IN |
notInArray(col, [a, b]) |
NOT IN |
isNull(col) |
IS NULL |
isNotNull(col) |
IS NOT NULL |
and(...) |
AND |
or(...) |
OR |
not(...) |
NOT |
desc(col) / asc(col) |
ORDER BY |
七、Drizzle Studio
1. 启动
pnpm db:studio
浏览器自动打开 https://local.drizzle.studio。
2. 能做什么
- 查看所有表结构
- 浏览/筛选/排序表数据
- 手动 CRUD
- 看表关系图(有外键时)
3. 替代品对比
| 工具 | 价格 | 平台 |
|---|---|---|
| Drizzle Studio | 免费 | 网页 |
| TablePlus | 付费 | Mac/Win |
| DataGrip (JetBrains) | 付费 | Mac/Win/Linux |
| pgAdmin | 免费 | 网页 |
调试阶段 Drizzle Studio 够用,生产场景一般用 TablePlus / DataGrip。
八、关键细节与坑
1. .returning() ------ PostgreSQL 专属
PostgreSQL 默认 INSERT/UPDATE/DELETE 不返回数据。.returning() 让操作同时返回完整记录:
scss
// ❌ 多此一举
await db.insert(chats).values(body)
const [newChat] = await db.select().from(chats).where(eq(chats.id, '...'))
// ✅ 一次完成
const [newChat] = await db.insert(chats).values(body).returning()
MySQL 不支持 .returning(),需要单独 SELECT。
2. 为什么字段用 snake_case,代码用 camelCase
css
createdAt: timestamp('created_at').defaultNow().notNull()
// ↑ TS 代码用驼峰 ↑ 数据库列名用下划线
数据库列习惯用 snake_case(SQL 不区分大小写,引号会麻烦),代码习惯用 camelCase。Drizzle 帮你自动映射。
3. UUID vs 自增 ID
| 方案 | 优点 | 缺点 |
|---|---|---|
| 自增 integer | 短、查询快 | 暴露数据量、分布式冲突 |
| UUID | 不暴露、可分布式、可前生成 | 长、占空间多 |
AI 时代主流用 UUID。uuid().defaultRandom() 让数据库自动生成,不依赖应用层。
4. eq() 是查询构造器,不是 JS 表达式
perl
.where(eq(chats.id, id)) // ✅ 这会被翻译成 SQL
.where(chats.id === id) // ❌ 这是 JS 表达式,Drizzle 不认
eq 等查询函数本质是"构造 SQL 片段",不是 JS 运算。
5. 环境变量管理
.env 文件:
bash
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/legal_agent
.gitignore 必加:
bash
.env
启动时通过 --env-file=.env 自动加载(Node 20+)。
6. db.execute() 写原生 SQL 兜底
Drizzle 没覆盖的 PostgreSQL 高级功能,可以原生 SQL 兜底:
javascript
import { sql } from 'drizzle-orm'
const result = await db.execute(sql`SELECT version()`)
7. 不要在循环里逐条 INSERT
scss
// ❌ 慢,N 次数据库往返
for (const item of items) {
await db.insert(chats).values(item)
}
// ✅ 一次往返,批量插入
await db.insert(chats).values(items)
8. Transaction 事务
dart
await db.transaction(async (tx) => {
await tx.insert(chats).values({ title: 'A' })
await tx.insert(chats).values({ title: 'B' })
// 任何一步失败,整个事务回滚
})
九、完整最小项目结构
bash
my-project/
├── docker-compose.yml # PostgreSQL 容器
├── drizzle.config.ts # Drizzle Kit 配置
├── .env # 数据库连接字符串
├── .gitignore
├── package.json
├── tsconfig.json
└── src/
├── index.ts # Hono 入口
└── db/
├── index.ts # 数据库连接
├── schema.ts # 表结构定义
└── migrations/
└── 0000_xxx.sql # 自动生成的 migration
十、参考资源
- Drizzle ORM 官方文档:orm.drizzle.team
- Hono 官方文档:hono.dev
- PostgreSQL 官方文档:www.postgresql.org/docs/16/
- Docker Desktop:www.docker.com/products/do...
后续笔记会加上:外键关联 / Drizzle Relations API / pgvector 向量搜索 / 多表 join 实战。