Skip to content

集成 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 push

push 适合本地快速验证,不适合多人协作和生产环境。正式流程应提交 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-validatorzod 或其他校验方案处理请求输入。

事务

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,避免部分语句不在同一个事务中执行。

生命周期

DatabaseProvideronModuleDestroy() 中关闭连接池。若应用需要在收到 SIGTERM 等系统信号时执行生命周期钩子,应在入口启用关闭钩子:

ts
import { NestFactory } from '@nestjs/core'

import { AppModule } from './app.module'

const app = await NestFactory.create(AppModule)

app.enableShutdownHooks()

参考

基于 MIT 许可发布