真香!NestJS + Prisma 组合拳,让后端开发效率提升 200%
用过 Prisma 但忘了?别慌,这篇文章带你 10 分钟光速复健!
作为一名 Node.js 后端开发者,最近两年我最庆幸的事,就是把项目中的 TypeORM 替换成了 Prisma。
如果你在用 NestJS 写后端,且还在手动写 SQL 或者被传统 ORM 复杂的实体配置搞得头大,那么Prisma绝对是你的"真命天子"。虽然我之前用过 Prisma,但一段时间没碰手就生了(相信不止我一个)。
今天,我就带着大家从零开始,在 NestJS 中把 Prisma 的核心用法彻底"捡"回来。文章涵盖了配置、CRUD、关联查询、事务 以及极易被忽略的踩坑点。
一、为什么是 Prisma?(传统 ORM 的痛点)
在展开代码前,我们先对齐一下"颗粒度"。NestJS 官方推荐了多种 ORM,但 Prisma 能脱颖而出,靠的是这三点:
- 类型安全降维打击 :Prisma Client 会根据你的数据模型自动生成 TS 类型 。你在
this.prisma.user.findUnique()时,IDE 会智能提示所有字段,字段名拼写错误在编译阶段就直接报红,彻底告别sql字符串的噩梦。 - 声明式建模 :不需要写繁琐的
@Entity()装饰器,一个独立的schema.prisma文件清晰定义了所有表和关系。 - 流畅的关联查询 :原生支持
include和嵌套create,写复杂查询就像写 JSON 一样丝滑。
二、极速搭建:集成到 NestJS
1. 安装依赖
在 NestJS 项目根目录执行:
bash
npm install @prisma/client prisma --save-dev
npx prisma init
执行后,会自动生成 .env 文件和 prisma/schema.prisma 文件。
2. 配置数据库连接 (.env)
修改 .env 里的 DATABASE_URL(以 MySQL 为例):
env
DATABASE_URL="mysql://root:123456@localhost:3306/my_nest_db"
3. 定义数据模型 (schema.prisma)
我们以一个简单的 用户-文章 模型为例,展示一对多关系:
prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
4. 执行迁移(生成数据库表)
这一步会生成 SQL 并同步到数据库,同时生成类型安全的 Prisma Client:
bash
npx prisma migrate dev --name init
三、NestJS 中的优雅封装(重点)
不要直接把 PrismaClient 写在 Controller 里,我们需要利用 NestJS 的 DI(依赖注入)机制,把它封装成一个全局共享的 Service。
创建 src/prisma/prisma.service.ts:
typescript
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() {
super({
// 开发时打印 SQL 日志,方便调试
log: process.env.NODE_ENV === 'development' ? ['query', 'info', 'warn', 'error'] : ['error'],
});
}
async onModuleInit() {
await this.$connect(); // 应用启动时自动连接
}
async onModuleDestroy() {
await this.$disconnect(); // 应用关闭时断开连接
}
}
记得在 AppModule 中注册该 Service,这样我们就可以在任何地方通过 @Inject() 使用了。
四、实战 CRUD(把遗忘的捡回来)
现在我们写一个 UserService,复健一下增删改查的写法。
1. 查询(Find)
typescript
// 查询单个用户(按唯一键)
async findOne(id: number) {
return this.prisma.user.findUnique({
where: { id },
});
}
// 查询列表(带分页和模糊搜索)
async findAll(name?: string) {
return this.prisma.user.findMany({
where: {
name: { contains: name, mode: 'insensitive' }, // 忽略大小写模糊查询
},
skip: 0,
take: 10,
orderBy: { id: 'desc' },
});
}
2. 创建与更新(Create & Update)
typescript
// 新增用户
async create(data: { email: string; name: string }) {
return this.prisma.user.create({
data,
});
}
// 更新用户
async update(id: number, data: { name?: string }) {
return this.prisma.user.update({
where: { id },
data,
});
}
3. 删除(Delete)
typescript
// 物理删除(慎用,实际业务更推荐软删除)
async remove(id: number) {
return this.prisma.user.delete({
where: { id },
});
}
五、Prisma 的"杀手锏":关联操作
这是 Prisma 最爽的地方,不需要写复杂的 left join 语句。
场景 1:查询用户顺便把它的所有文章带出来
typescript
async findUserWithPosts(id: number) {
return this.prisma.user.findUnique({
where: { id },
include: {
posts: true, // 自动关联查询!
},
});
}
场景 2:嵌套创建(创建一个用户的同时,直接给他发一篇文章) 这在传统 ORM 里往往需要两次 save 操作,而在 Prisma 中一步到位:
typescript
async createUserAndPost() {
return this.prisma.user.create({
data: {
email: 'tom@qq.com',
name: 'Tom',
posts: {
create: { title: '我的第一篇 Prisma 文章', content: '真香!' },
},
},
include: { posts: true }, // 创建后把关联结果也返回
});
}
六、进阶:事务与原生 SQL(兜底方案)
1. 事务(Transaction)
银行转账、扣库存等场景,必须保证要么全成功,要么全失败。
typescript
async transfer() {
return this.prisma.$transaction([
this.prisma.user.update({ where: { id: 1 }, data: { name: 'A' } }),
this.prisma.user.update({ where: { id: 2 }, data: { name: 'B' } }),
]);
}
2. 原生 SQL(当 Prisma 语法不支持复杂查询时)
虽然 Prisma 很强,但遇到极其复杂的报表查询时,直接写 SQL 更灵活。
typescript
await this.prisma.$queryRaw`SELECT * FROM User WHERE email LIKE ${'%gmail%'}`;
七、给新手的避坑指南(血泪教训)
- 修改模型后必须迁移 :改完
schema.prisma,一定要跑npx prisma migrate dev,否则数据库结构没变,代码会报错。 - 改模型后重启 Nest 服务 :由于 Prisma Client 是生成在
node_modules里的,Nest 的热重载(HMR)有时感知不到变化。如果遇到类型提示不更新,建议Ctrl+C后重新npm run start:dev。 findUniquevsfindFirst:findUnique的where条件必须包含唯一字段 (如id或@unique标记的字段),否则会报错。如果想按普通字段查,请用findFirst。- 软删除实现 :Prisma 没有内置
@SoftDelete,需要在模型中手动加deletedAt DateTime?,并在findMany时全局过滤deletedAt: null。
八、总结
将 Prisma 接入 NestJS ,本质上就是把 MVC 架构 中的 Model 层换上了一把"利刃"。Controller 接收 RESTful 请求,Service 调用 Prisma 操作数据库,整个过程类型安全且极富开发乐趣。
如果你还在因为复杂的 SQL 联表查询而加班,不妨把 Prisma 引入你的下一个项目。相信我,一旦用上,你就再也回不去了。
如果这篇文章帮你找回了 Prisma 的手感,欢迎点赞、收藏、转发! 如果在实践中遇到报错,欢迎在评论区留言,我们一起排查。😊
(注:本文代码基于 NestJS v10 + Prisma v5,遵循 MIT 协议,可放心食用。)