1. 为什么选择Nest.js + Prisma这套技术栈?
在全栈开发领域,技术选型往往决定了项目的开发效率和后期维护成本。我选择Nest.js + Prisma这套组合主要基于以下几个实际考量:
首先,Nest.js作为Node.js的后端框架,完美融合了TypeScript的类型安全和Angular风格的模块化设计。在实际项目中,我发现它的依赖注入系统和模块划分特别适合中大型项目。比如一个电商系统可以清晰地划分为用户模块、商品模块、订单模块等,每个模块包含自己的控制器、服务层和数据访问层。
Prisma作为新一代ORM工具,解决了传统ORM的N+1查询问题。我在实际使用中发现,它的类型安全特性可以在编译阶段就发现大部分数据访问层的错误。比如当你尝试查询一个不存在的字段时,TypeScript会直接报错,而不是等到运行时才崩溃。
提示:对于刚从Express转Nest.js的开发者,建议先理解装饰器(@Injectable、@Controller等)和模块系统的设计理念,这会大大降低学习曲线。
2. 环境准备与项目初始化
2.1 创建Nest.js项目
首先确保系统已安装Node.js(建议16.x以上版本)和npm/yarn。然后执行以下命令创建项目:
bash复制npm i -g @nestjs/cli
nest new nest-prisma-demo
cd nest-prisma-demo
这里我推荐使用pnpm作为包管理器,它能显著减少node_modules的体积。安装完成后,项目结构应该包含:
- src/
- app.controller.ts
- app.module.ts
- app.service.ts
- main.ts
2.2 集成Prisma
在项目根目录下执行:
bash复制pnpm add -D prisma
pnpm add @prisma/client
npx prisma init
这会在项目中创建prisma目录和.env文件。我建议在.env中配置数据库连接:
env复制DATABASE_URL="postgresql://user:password@localhost:5432/nest-prisma?schema=public"
注意:在实际部署时,一定要将.env加入.gitignore,避免敏感信息泄露。
3. 数据模型设计与Prisma迁移
3.1 定义数据模型
在prisma/schema.prisma文件中定义你的数据模型。以一个博客系统为例:
prisma复制model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
3.2 执行数据库迁移
运行以下命令将模型同步到数据库:
bash复制npx prisma migrate dev --name init
这个命令会:
- 生成迁移SQL文件
- 应用到数据库
- 生成Prisma Client
我在实际项目中遇到的一个坑是:当修改已有模型时,一定要先备份数据,因为某些修改会导致数据丢失。
4. 在Nest.js中集成Prisma Client
4.1 创建Prisma服务
生成一个Prisma服务模块:
bash复制nest generate module prisma
nest generate service prisma
在prisma.service.ts中:
typescript复制import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
4.2 全局导入Prisma模块
在app.module.ts中:
typescript复制import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [PrismaModule],
})
export class AppModule {}
这样在任何需要访问数据库的模块中,都可以通过依赖注入使用PrismaService。
5. 实现完整的CRUD功能
5.1 用户模块实现
生成用户模块:
bash复制nest generate module user
nest generate controller user
nest generate service user
在user.service.ts中实现CRUD:
typescript复制@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async create(data: Prisma.UserCreateInput) {
return this.prisma.user.create({ data });
}
async findAll() {
return this.prisma.user.findMany();
}
async findOne(id: number) {
return this.prisma.user.findUnique({ where: { id } });
}
async update(id: number, data: Prisma.UserUpdateInput) {
return this.prisma.user.update({ where: { id }, data });
}
async remove(id: number) {
return this.prisma.user.delete({ where: { id } });
}
}
5.2 控制器层实现
在user.controller.ts中:
typescript复制@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
create(@Body() createUserDto: Prisma.UserCreateInput) {
return this.userService.create(createUserDto);
}
@Get()
findAll() {
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: Prisma.UserUpdateInput) {
return this.userService.update(+id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
}
6. 高级查询与性能优化
6.1 关联查询实现
Prisma的强大之处在于处理关联查询。比如获取用户及其所有文章:
typescript复制async findUserWithPosts(id: number) {
return this.prisma.user.findUnique({
where: { id },
include: { posts: true },
});
}
6.2 分页与排序
实现带分页的查询:
typescript复制async findPaginated(skip: number, take: number) {
return this.prisma.user.findMany({
skip,
take,
orderBy: { createdAt: 'desc' },
});
}
6.3 事务处理
对于需要原子性操作的情况,使用事务:
typescript复制async transferCredits(fromId: number, toId: number, amount: number) {
return this.prisma.$transaction([
this.prisma.user.update({
where: { id: fromId },
data: { credit: { decrement: amount } },
}),
this.prisma.user.update({
where: { id: toId },
data: { credit: { increment: amount } },
}),
]);
}
7. 常见问题与调试技巧
7.1 处理Prisma连接池耗尽
在生产环境中,可能会遇到连接池耗尽的问题。解决方案是在PrismaService中添加连接管理:
typescript复制@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
7.2 性能监控
启用Prisma的日志功能可以帮助调试性能问题:
typescript复制constructor() {
super({
log: ['query', 'info', 'warn', 'error'],
});
}
7.3 处理复杂查询
对于特别复杂的查询,可以考虑使用原始SQL:
typescript复制async complexQuery() {
return this.prisma.$queryRaw`
SELECT u.*, COUNT(p.id) as post_count
FROM "User" u
LEFT JOIN "Post" p ON u.id = p."authorId"
GROUP BY u.id
`;
}
8. 测试策略与部署建议
8.1 单元测试配置
为PrismaService编写测试时,可以使用jest-mock-extended:
typescript复制import { mockDeep } from 'jest-mock-extended';
describe('UserService', () => {
let prisma: DeepMockProxy<PrismaClient>;
let service: UserService;
beforeEach(() => {
prisma = mockDeep<PrismaClient>();
service = new UserService(prisma);
});
it('should create user', async () => {
prisma.user.create.mockResolvedValue({
id: 1,
email: 'test@example.com',
name: 'Test User',
});
await expect(
service.create({ email: 'test@example.com' })
).resolves.toHaveProperty('id', 1);
});
});
8.2 生产环境部署
部署到生产环境时需要注意:
- 使用环境变量管理数据库连接
- 配置适当的连接池大小
- 启用Prisma的加速特性:
prisma复制generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
8.3 CI/CD集成
在CI流程中添加Prisma迁移:
yaml复制- run: npx prisma migrate deploy
- run: npx prisma generate
我在实际项目中发现,这套技术栈特别适合需要快速迭代的全栈项目。Prisma的类型安全让前端和后端开发者的协作更加顺畅,而Nest.js的模块化设计则让项目结构保持清晰。对于刚接触这套技术栈的开发者,建议先从简单的CRUD开始,逐步探索更高级的特性如中间件、拦截器和GraphQL集成。
