项目初始化
使用脚手架搭建项目
连接数据库pg 我们使用docker搭建数据库环境
项目文件根目录下方创建,docker-compose.yaml
services:
postgres:
# 拉取最新版本的 postgres 镜像
image: postgres
# 隐射容器的 5432 端口到主机的 5432 端口
ports:
- 5432:5432
# 设置容器的环境变量
environment:
POSTGRES_PASSWORD: 123456
POSTGRES_DB: nestjs-drizzle
使用GUI连接数据库

项目安装依赖
pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg
连接数据库
修改.env文件 用于配置连接
APP_NAME=nest-cli
PORT=8900
CORS=false
# 数据库配置
DATABASE_URL=postgres://postgres:123456@localhost:5432/nestjs-drizzle
初始化
生成module文件
nest g mo database/drizzle/database --flat
// database/drizzle/database.module.ts
import { Global, Module } from '@nestjs/common'
import { drizzle, NodePgDatabase } from 'drizzle-orm/node-postgres'
import { ConfigService } from '@nestjs/config'
import { Pool } from 'pg'
import * as schema from './schemas'
export const Drizzle = Symbol('drizzle-connection')
export type DrizzleDB = NodePgDatabase<typeof schema>
@Global()
@Module({
providers: [
{
provide: Drizzle,
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const pool = new Pool({
// 数据库/Redis/JWT 密钥等“缺了就不能跑”的配置,全用 getOrThrow
// 可选配置才用普通 .get() 并给默认值
connectionString: configService.getOrThrow('DATABASE_URL'),
})
return drizzle(pool, { schema }) as DrizzleDB
},
},
],
exports: [Drizzle],
})
export class DatabaseModule {}
创建表
创建schema文件 /drizzle/schemas
index.ts
// schema/index.ts
export * from './user.schema'
user.schema.ts
import { pgTable, serial, text } from 'drizzle-orm/pg-core'
export const userSchema = pgTable('user', {
id: serial('id').primaryKey(),
email: text('email').unique().notNull(),
password: text('password').notNull(),
})
迁移
在根目录下创建 drizzle.config.ts 文件,运行 Drizzle 迁移命令等。
drizzle-kit generate: 根据 schema 变化生成迁移文件。drizzle-kit migrate: 执行生成的迁移文件,将数据库更新到最新的 schema 状态。
pnpm add dotenv
import { defineConfig } from 'drizzle-kit'
import { config } from 'dotenv'
config({ path: './.env' })
export default defineConfig({
// 指定schema路径
schema: './src/database/drizzle/schemas/**.schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})
使用
/user.service.ts
import { Drizzle, type DrizzleDB } from '@/database/drizzle/database.module'
import { userSchema } from '@/database/drizzle/schemas'
import { Inject, Injectable } from '@nestjs/common'
@Injectable()
export class UserService {
constructor(@Inject(Drizzle) private readonly db: DrizzleDB) {}
findAll() {
// 查询所有用户
return this.db.select().from(userSchema)
}
}
也可以这样写
this.db.query.userSchema.findMany()
第一种写法类似 sql 写法 第二种写法更加简便,建议简单的可以使用 query 语法
一对多模型
一个用户可以有多个帖子,多个帖子拥有一个用户
新建帖子模块(posts)
nest g mo modules/posts --no-spec
nest g co modules/posts --no-spec
nest g s modules/posts --no-spec
抽离 时间 公共模块
// global.schema.ts
import { timestamp } from 'drizzle-orm/pg-core'
export const baseSchema = {
createAt: timestamp('create_at').defaultNow().notNull(),
updateAt: timestamp('update_at').defaultNow().notNull(),
}
posts 帖子数据模型
// schemas/posts.schema.ts
import { boolean } from 'drizzle-orm/pg-core'
import { pgTable, serial, text } from 'drizzle-orm/pg-core'
import { baseSchema } from './global.schema'
import { integer } from 'drizzle-orm/pg-core'
import { userSchema } from './user.schema'
import { relations } from 'drizzle-orm'
export const postsSchema = pgTable('posts', {
id: serial('id').primaryKey(),
content: text('content'),
published: boolean('published').default(false),
...baseSchema,
userId: integer('user_id').references(() => userSchema.id),
})
// 多对一(posts 多条 → user 一条)
export const postsRelations = relations(postsSchema, ({ one }) => ({
// 给这条关系起个名 user
user: one(userSchema, {
fields: [postsSchema.userId], // 外键列(存在于 posts 表)
references: [userSchema.id], // 被引用列(存在于 user 表)
}),
}))
user 数据模型补充 对应关系
import { relations } from 'drizzle-orm'
import { pgTable, serial, text } from 'drizzle-orm/pg-core'
import { postsSchema } from './posts.schema'
export const userSchema = pgTable('user', {
id: serial('id').primaryKey(),
email: text('email').unique().notNull(),
password: text('password').notNull(),
})
//user 一条 → posts 多条)
export const userRelations = relations(userSchema, ({ many }) => ({
posts: many(postsSchema),
}))
查询使用
import { Drizzle, type DrizzleDB } from '@/database/drizzle/database.module'
import { postsSchema } from '@/database/drizzle/schemas'
import { Inject, Injectable } from '@nestjs/common'
@Injectable()
export class PostsService {
constructor(@Inject(Drizzle) private readonly db: DrizzleDB) {}
findPostsAll() {
// 关系用户表所有内容
return this.db.query.postsSchema.findMany({ with: { user: true } })
}
async createPosts(posts: typeof postsSchema.$inferInsert) {
await this.db.insert(postsSchema).values(posts)
}
}
一对一模型
一个用户拥有一份档案信息
/schemas/profile.schema.ts
import { pgTable, serial, integer, text } from 'drizzle-orm/pg-core'
import { userSchema } from './user.schema'
import { relations } from 'drizzle-orm'
export const profileSchema = pgTable('profile', {
id: serial('id').primaryKey(),
age: integer('age').default(0),
biography: text('biography'),
userId: integer('user_id').references(() => userSchema.id),
})
// 一对一关联用户表
export const profileRelations = relations(profileSchema, ({ one }) => ({
user: one(userSchema, {
fields: [profileSchema.userId],
references: [userSchema.id],
}),
}))
/schemas/user.schema.ts
import { relations } from 'drizzle-orm'
import { pgTable, serial, text } from 'drizzle-orm/pg-core'
import { postsSchema } from './posts.schema'
import { profileSchema } from './profile.schema'
export const userSchema = pgTable('user', {
id: serial('id').primaryKey(),
email: text('email').unique().notNull(),
password: text('password').notNull(),
})
//user 一条 → posts 多条)
export const userRelations = relations(userSchema, ({ many, one }) => ({
posts: many(postsSchema),
profile: one(profileSchema), // 关联档案信息表
}))
导出 使用,记得导出schema文件 !!!
import { Drizzle, type DrizzleDB } from '@/database/drizzle/database.module'
import { userSchema } from '@/database/drizzle/schemas'
import { profileSchema } from '@/database/drizzle/schemas/profile.schema'
import { Inject, Injectable } from '@nestjs/common'
@Injectable()
export class UserService {
constructor(@Inject(Drizzle) private readonly db: DrizzleDB) {}
findAll() {
// return this.db.select().from(userSchema)
// 关联查询 两个表进行数据回显
return this.db.query.userSchema.findMany({ with: { profile: true, posts: true } })
}
async createProfile(profile: typeof profileSchema.$inferInsert) {
await this.db.insert(profileSchema).values(profile)
}
}
多对多模型
分类 一个文章可以拥有多分类 一个分类也可以拥有多个文章
nest g mo modules/categories --no-spec
nest g co modules/categories --no-spec
nest g s modules/categories --no-spec
多对多 就是中间建立一张关系表 对应两个表的id
然后两个表分别取映射 many 到这张中间关系表去进行连接
/schemas/categories.schema.ts
import { pgTable, text, serial, integer } from 'drizzle-orm/pg-core'
import { postsSchema } from './posts.schema'
import { primaryKey } from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'
// 分类表
export const categoriesSchema = pgTable('categories', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
})
// 分类表关系
export const categoriesSchemaRelations = relations(categoriesSchema, ({ many }) => ({
postsToCategories: many(postsToCategoriesSchema),
}))
// 文章分类关联表
export const postsToCategoriesSchema = pgTable(
'posts_to_categories',
{
postsId: integer('posts_id')
.notNull()
.references(() => postsSchema.id),
categoriesId: integer('categories_id')
.notNull()
.references(() => categoriesSchema.id),
},
(t) => [primaryKey({ columns: [t.postsId, t.categoriesId] })],
)
// 文章分类关联表关系
export const postsToCategoriesSchemaRelations = relations(postsToCategoriesSchema, ({ one }) => ({
posts: one(postsSchema, {
fields: [postsToCategoriesSchema.postsId],
references: [postsSchema.id],
}),
categories: one(categoriesSchema, {
fields: [postsToCategoriesSchema.categoriesId],
references: [categoriesSchema.id],
}),
}))
/schemas/posts.schema.ts
import { boolean } from 'drizzle-orm/pg-core'
import { pgTable, serial, text } from 'drizzle-orm/pg-core'
import { baseSchema } from './global.schema'
import { integer } from 'drizzle-orm/pg-core'
import { userSchema } from './user.schema'
import { relations } from 'drizzle-orm'
import { postsToCategoriesSchema } from './categories.schema'
export const postsSchema = pgTable('posts', {
id: serial('id').primaryKey(),
content: text('content'),
published: boolean('published').default(false),
...baseSchema,
userId: integer('user_id').references(() => userSchema.id),
})
// 多对一(posts 多条 → user 一条)
export const postsRelations = relations(postsSchema, ({ one, many }) => ({
// 给这条关系起个名 user
user: one(userSchema, {
fields: [postsSchema.userId], // 外键列(存在于 posts 表)
references: [userSchema.id], // 被引用列(存在于 user 表)
}),
postsToCategories: many(postsToCategoriesSchema), // 映射关系表
}))
使用 这样就会返回关系表中的id
findPostsAll() {
return this.db.query.postsSchema.findMany({ with: { user: true, postsToCategories: true } })
}
eq 根据id进行查询
根据id 去进行比对数据表查询
// 语法
where: eq(postsSchema.id, postsId)
完整示例:
// controller 层
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.postsService.findPosts(id)
}
// service 层
async findPosts(postsId: number) {
return this.db.query.postsSchema.findFirst({
where: eq(postsSchema.id, postsId),
with: { user: true, postsToCategories: true },
})
}
更新操作update
传递 帖子id 并更新该条数据,如果传入的id 不存在数据库将会返回一个空数组
async updatePost(postsId: number, posts: typeof postsSchema.$inferInsert) {
// returning 表示返回更新后的数据
const updatedPosts = await this.db.update(postsSchema)
.set(posts)
.where(eq(postsSchema.id, postsId))
.returning()
if (updatedPosts.length === 0) {
throw new NotFoundException('帖子不存在')
}
return updatedPosts[0]
}
数据库事务
创建帖子的时候可以同步创建分类标签,如果帖子创建失败那么创建的标签也会被回滚
核心代码:
async createPosts(posts: typeof postsSchema.$inferInsert, categories?: string) {
// transaction 声明事务 tx 表示数据操作 所有再事务里面 需要对数据库进行操作的 都需要使用 tx
// 都使用 tx 进行操作
await this.db.transaction(async (tx) => {
const postsArr = await tx.insert(postsSchema).values(posts).returning({ id: postsSchema.id })
if (categories) {
const { id } = await this.categoriesService.create({ name: categories }, tx)
await this.categoriesService.addCategory({ postsId: postsArr[0].id, categoriesId: id }, tx)
}
})
}
async create(categories: typeof categoriesSchema.$inferInsert, tx?: DrizzleDB) {
const category = await (tx || this.db)
.insert(categoriesSchema)
.values(categories)
.returning({ id: categoriesSchema.id })
return category[0]
}
async addCategory(postsToCategories: typeof postsToCategoriesSchema.$inferInsert, tx?: DrizzleDB) {
await (tx || this.db).insert(postsToCategoriesSchema).values(postsToCategories)
}
实现增删改查
- Select 查
- Insert 创建
- Update 更新
- Delete 删除
- Filters 查询条件,如 where 中的各种条件的用法说明
- Utils 工具,主要是查数量
- Joins 关联
- Magic sql operator 自定义 sql 语句
基础增删改查:
db.insert(usersTable).values(data);
db.select().from(usersTable);
db.select().from(usersTable).where(eq(usersTable.id, id));
db.update(postsTable).set(data).where(eq(postsTable.id, id));
db.delete(usersTable).where(eq(usersTable.id, id));
查询近24小时文章(灵活拼接 sql 语句):
db
.select({
id: postsTable.id,
title: postsTable.title,
})
.from(postsTable)
.where(between(postsTable.createdAt, sql`now() - interval '1 day'`, sql`now()`))
.orderBy(asc(postsTable.title), asc(postsTable.id))
.limit(pageSize)
.offset((page - 1) * pageSize);
分页+关联查询+排序,其中关联通过getTableColumns查询主表所有字段,以及自定义一个关联查询的字段:
db.select({
...getTableColumns(usersTable),
postsCount: count(postsTable.id),
})
.from(usersTable)
.leftJoin(postsTable, eq(usersTable.id, postsTable.userId))
.groupBy(usersTable.id)
.orderBy(asc(usersTable.id))
.limit(pageSize)
.offset((page - 1) * pageSize);
join 关联查询返回的结果无法自动映射,可手动增加转换:
const rows = db.select({
user: users,
pet: pets,
}).from(users).leftJoin(pets, eq(users.id, pets.ownerId)).all();
const result = rows.reduce<Record<number, { user: User; pets: Pet[] }>>(
(acc, row) => {
const user = row.user;
const pet = row.pet;
if (!acc[user.id]) {
acc[user.id] = { user, pets: [] };
}
if (pet) {
acc[user.id].pets.push(pet);
}
return acc;
},
{}
);
分页查询+total 总数计算 使用事务保持数据更新一致性
// 使用 事务 (快照)保证数据的一致性
return this.db.transaction(async (tx) => {
const userList = await tx
.select()
.from(userSchema)
.limit(size)
.offset((current - 1) * size)
// 统计总数
const [countNumber] = await this.db.select({ count: count() }).from(userSchema)
const pages = Math.ceil(countNumber.count / size)
return { current, data: userList, total: countNumber.count, pages }
})
- 基础实现
@Injectable()
export class UserService {
constructor(private readonly drizzle: DrizzleService) {}
async create(createUserDto: CreateUserDto) {
const result = await this.drizzle.db.insert(schema.user).values(createUserDto).returning();
return result;
}
async findOne(id: number) {
const [user] = await this.drizzle.db.select().from(schema.user).where(eq(schema.user.id, id));
return user;
}
async update(id: number, updateUserDto: UpdateUserDto) {
const user = this.drizzle.db
.update(schema.user)
.set(updateUserDto)
.where(eq(schema.user.id, id));
return user;
}
async remove(id: number) {
const [user] = await this.drizzle.db
.delete(schema.user)
.where(eq(schema.user.id, id))
.returning();
return user;
}
}
package.json 中的脚本
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
}
- db:generate 声明时或后续 Schema 更改时基于 Drizzle Schema 生成 SQL 迁移
- db:migrate 运行迁移后,Drizzle Kit 会将成功应用的迁移记录保存到数据库中。
- db:push 允许您直接将您的架构和后续架构更改推送到数据库
- db:studio 本地数据库可视化面板
说明:
| 阶段 | 命令 |
|---|---|
| 第一次初始化 | pnpm db:generate + pnpm db:migrate |
| 开发中快速迭代 | pnpm db:push(结构稳定后再 generate→migrate) |
| 上线前 | 确保 generate→migrate 把最新迁移包打进去 |
| 调试数据 | pnpm db:studio 随时开 |
初始化执行一次 generate→migrate
此后 每次新建表或是改表的数据结构 就执行 push,上线前再执行 generate→migrate即可