主题
集成 Drizzle
以 PostgreSQL 为例
安装依赖
bash
pnpm add @nestjs/config drizzle-orm pg
pnpm add -D drizzle-kit @types/pg详细说明
drizzle-kit:Drizzle 迁移工具。drizzle-orm:Drizzle ORM。pg:PostgreSQL 驱动。@types/pg:提供 PostgreSQL 驱动的类型定义。@nestjs/config:通过 NestJS 依赖注入读取数据库连接配置。
环境变量
md
DATABASE_URL=postgres://app_user:app_password@localhost:5432/app_db生产环境不要提交真实连接串。应用连接数据库应使用独立业务用户,不要使用 PostgreSQL 超级用户。
推荐目录
bash
project/
├── src/ # 应用源码
│ ├── infrastructure/ # 数据库等基础设施
│ │ └── database/
│ │ ├── database.module.ts # 注册并导出数据库 Provider
│ │ ├── database.provider.ts # 创建连接池和 Drizzle 实例
│ │ └── schema/ # Drizzle 数据表定义
│ │ ├── index.ts # 统一导出全部 Schema
│ │ └── users.schema.ts # users 表定义
│ └── modules/ # 按业务域组织功能模块
│ └── users/
│ ├── users.module.ts # users 模块入口
│ └── users.service.ts # users 业务与数据库操作
├── drizzle/ # Drizzle 生成的迁移文件
└── drizzle.config.ts # Drizzle Kit 配置定义 Schema
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(),
})ts
export * from './users.schema'常见字段习惯:
- 主键优先使用
uuid或 PostgreSQL identity。 - 时间点优先使用
timestamp(..., { withTimezone: true })。 - 金额使用
numeric,不要使用浮点数。 - 需要 JSON 查询和索引时使用
jsonb。
配置 Drizzle Kit
ts
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
dialect: 'postgresql',
schema: './src/infrastructure/database/schema/index.ts',
out: './drizzle',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})生成和执行迁移
生成迁移:
bash
pnpm drizzle-kit generate执行迁移:
bash
pnpm drizzle-kit migrate开发阶段可以使用 push 快速同步 schema:
bash
pnpm drizzle-kit pushpush 适合本地快速验证,不适合多人协作和生产环境。正式流程应提交 drizzle/ 目录下生成的迁移文件。
封装数据库 Provider
ts
import { Injectable, OnModuleDestroy } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { drizzle } from 'drizzle-orm/node-postgres'
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
import * as schema from './schema'
@Injectable()
export class DatabaseProvider implements OnModuleDestroy {
private readonly pool: Pool
readonly db: NodePgDatabase<typeof schema>
constructor(configService: ConfigService) {
this.pool = new Pool({
connectionString: configService.getOrThrow<string>('DATABASE_URL'),
})
this.db = drizzle({
client: this.pool,
schema,
})
}
onModuleDestroy() {
return this.pool.end()
}
}ts
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { DatabaseProvider } from './database.provider'
@Module({
imports: [ConfigModule],
providers: [DatabaseProvider],
exports: [DatabaseProvider],
})
export class DatabaseModule {}数据库模块默认不是全局模块。需要访问数据库的业务模块应显式导入 DatabaseModule,使模块依赖保持清晰。
在业务服务中使用
ts
import { Module } from '@nestjs/common'
import { DatabaseModule } from '../../infrastructure/database/database.module'
import { UsersService } from './users.service'
@Module({
imports: [DatabaseModule],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}ts
import { Injectable } from '@nestjs/common'
import { eq } from 'drizzle-orm'
import { DatabaseProvider } from '../../infrastructure/database/database.provider'
import { users } from '../../infrastructure/database/schema'
@Injectable()
export class UsersService {
constructor(private readonly database: DatabaseProvider) {}
findAll() {
return this.database.db.select().from(users)
}
findById(id: string) {
return this.database.db.query.users.findFirst({
where: eq(users.id, id),
})
}
create(input: { name: string; email: string }) {
return this.database.db.insert(users).values(input).returning()
}
}返回值由 Drizzle 根据 schema 推导。需要 DTO 校验时,仍然使用 NestJS 的 class-validator、zod 或其他校验方案处理请求输入。
事务
ts
await this.database.db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({ name: 'vfan', email: 'vfan@example.com' }).returning()
return user
})事务内部只使用 tx,不要混用外层 this.database.db,避免部分语句不在同一个事务中执行。
生命周期
DatabaseProvider 在 onModuleDestroy() 中关闭连接池。若应用需要在收到 SIGTERM 等系统信号时执行生命周期钩子,应在入口启用关闭钩子:
ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
const app = await NestFactory.create(AppModule)
app.enableShutdownHooks()