Drizzle ORM + PostgreSQL + Hono 学习笔记

Drizzle ORM + PostgreSQL + Hono 学习笔记

用 Drizzle ORM 操作 PostgreSQL 的完整知识点整理。涵盖 Docker 部署、Schema 设计、Migration 工作流、查询语法、常用细节。

目录


一、整体技术栈

工具 作用 包名
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=.envNode 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 Relations API / pgvector 向量搜索 / 多表 join 实战。

相关推荐
小瓦码J码2 小时前
轻量化线程池实战:忙时并发、闲时归零,搞定周期批量任务
java·后端
贵州数擎科技有限公司2 小时前
雨滴特效的 Three.js 实现
前端·three.js
问心无愧05132 小时前
ctf show web入门98
android·前端·笔记
明豆2 小时前
HTTPS / TLS 1.3 深度解析 — Web 安全传输协议生产实战
前端·安全·https
Linsk2 小时前
Rollup 官方插件 @rollup/plugin-inject 详解
前端·rollup.js·前端工程化
2601_958492552 小时前
Performance Audit of Paper Boats Racing - HTML5 Racing Game
前端·html·html5
irving同学462382 小时前
TypeScript 后端入门全景:Hono + Zod + Drizzle + PostgreSQL
前端·后端
一致性2 小时前
项目总结:桌宠(Desktop Pet)
前端
百珏2 小时前
[灰度发布]:灰度流量如何匹配与识别:从特征补全到网关命中引擎
java·后端·架构