主题
集成 Drizzle
以 PostgreSQL 为例
安装依赖
bash
pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg详细说明
drizzle-kit:Drizzle 迁移工具。drizzle-orm:Drizzle ORM。pg:PostgreSQL 驱动。@types/pg:提供 PostgreSQL 驱动的类型定义。
环境变量
.env:
md
DATABASE_URL=postgres://app_user:app_password@localhost:5432/app_db生产环境不要提交真实连接串。应用连接数据库应使用独立业务用户,不要使用 PostgreSQL 超级用户。
推荐目录
bash
src
├── database
│ ├── database.module.ts
│ ├── database.provider.ts
│ └── schema
│ ├── index.ts
│ └── users.ts
└── users
├── users.module.ts
└── users.service.ts
drizzle
drizzle.config.tsdatabase 模块只负责数据库连接、Drizzle 实例和 schema 出口。业务模块通过注入数据库实例访问表。
定义 Schema
src/database/schema/users.ts:
ts
import { pgTable, timestamp, uuid, varchar } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(),
name: varchar('name', { length: 64 }).notNull(),
email: varchar('email', { length: 255 }).notNull().unique(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
})src/database/schema/index.ts:
ts
export * from './users'常见字段习惯:
- 主键优先使用
uuid或 PostgreSQL identity。 - 时间点优先使用
timestamp(..., { withTimezone: true })。 - 金额使用
numeric,不要使用浮点数。 - 需要 JSON 查询和索引时使用
jsonb。
配置 Drizzle Kit
drizzle.config.ts:
ts
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
dialect: 'postgresql',
schema: './src/database/schema/index.ts',
out: './drizzle',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})如果 CLI 不能自动加载 .env,可以安装 dotenv 并在配置文件顶部引入:
ts
import 'dotenv/config'生成和执行迁移
生成迁移:
bash
pnpm drizzle-kit generate执行迁移:
bash
pnpm drizzle-kit migrate开发阶段可以使用 push 快速同步 schema:
bash
pnpm drizzle-kit pushpush 适合本地快速验证,不适合多人协作和生产环境。正式流程应提交 drizzle/ 目录下生成的迁移文件。
封装数据库 Provider
src/database/database.provider.ts:
ts
import { Provider } from '@nestjs/common'
import { drizzle } from 'drizzle-orm/node-postgres'
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
import * as schema from './schema'
export const DRIZZLE = Symbol('DRIZZLE')
export type Database = NodePgDatabase<typeof schema>
export const databaseProvider: Provider = {
provide: DRIZZLE,
useFactory: () => {
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
})
return drizzle({
client: pool,
schema,
})
},
}src/database/database.module.ts:
ts
import { Global, Module } from '@nestjs/common'
import { databaseProvider } from './database.provider'
@Global()
@Module({
providers: [databaseProvider],
exports: [databaseProvider],
})
export class DatabaseModule {}@Global() 可以减少重复导入,但会降低依赖显式性。大型项目也可以不使用全局模块,在需要数据库的业务模块中显式导入 DatabaseModule。
在业务服务中使用
src/users/users.service.ts:
ts
import { Inject, Injectable } from '@nestjs/common'
import { eq } from 'drizzle-orm'
import { Database, DRIZZLE } from '../database/database.provider'
import { users } from '../database/schema'
@Injectable()
export class UsersService {
constructor(@Inject(DRIZZLE) private readonly db: Database) {}
findAll() {
return this.db.select().from(users)
}
findById(id: string) {
return this.db.query.users.findFirst({
where: eq(users.id, id),
})
}
create(input: { name: string; email: string }) {
return this.db.insert(users).values(input).returning()
}
}返回值由 Drizzle 根据 schema 推导。需要 DTO 校验时,仍然使用 NestJS 的 class-validator、zod 或其他校验方案处理请求输入。
事务
ts
await this.db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({ name: 'vfan', email: 'vfan@example.com' }).returning()
return user
})事务内部只使用 tx,不要混用外层 this.db,避免部分语句不在同一个事务中执行。
生命周期
上面的 Provider 没有显式关闭连接池。更完整的做法是把 Pool 封装成可关闭的服务,在 onModuleDestroy 中调用 pool.end()。
简单项目可以先使用 Provider 写法;长期运行服务建议改为 DatabaseService:
ts
import { Injectable, OnModuleDestroy } from '@nestjs/common'
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
import * as schema from './schema'
@Injectable()
export class DatabaseService implements OnModuleDestroy {
private readonly pool = new Pool({
connectionString: process.env.DATABASE_URL,
})
readonly db = drizzle({
client: this.pool,
schema,
})
onModuleDestroy() {
return this.pool.end()
}
}业务服务注入 DatabaseService 后使用 databaseService.db。
常见约定
- schema 文件按业务域拆分,再从
schema/index.ts统一导出。 - 每次修改 schema 后生成迁移,并检查生成 SQL。
- 生产环境只执行已提交的迁移,不使用
push。 - 数据库连接串放在环境变量或配置中心,不写入代码。
- 查询条件、分页、排序由业务层显式控制,不直接透传客户端字段。
- 复杂查询可以使用 Drizzle 的
sql模板,但要保持参数化,避免 SQL 注入。
