在现代后端开发中,NestJS 作为一款高效的 Node.js 框架,以其模块化设计和 TypeScript 支持深受开发者青睐。结合 Prisma 这个现代 ORM 工具,我们可以轻松处理数据库迁移、数据查询和验证逻辑,从而构建出可靠的 API 服务。本文将基于一个简单的帖子(Posts)管理场景,详细讲解 NestJS 的核心配置、Prisma 的集成、DTO(Data Transfer Object)的使用以及 class-validator 的验证机制。
NestJS 项目启动与全局配置
NestJS 的启动入口通常在 main.ts 或 bootstrap.ts 文件中。这里,我们使用 NestFactory 创建应用实例,并配置一些全局设置。以下是示例代码:
TypeScript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 将nestjs 像express 一样拥有一些服务
import { NestExpressApplication } from '@nestjs/platform-express';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
cors: true
});
app.setGlobalPrefix('api'); // 全局路由前缀/api
// 启用全局验证管道
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // 自动过滤dot 未定义的属性
forbidNonWhitelisted: true, // 遇到未定义的属性直接报错
transform: true, // "1" transform 1
}))
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
这个 bootstrap 函数是项目的起点。首先,导入 @nestjs/core 和根模块 AppModule,然后创建应用实例。我们指定了 NestExpressApplication 类型,以支持 Express 平台的扩展功能。配置项中启用了 CORS(cors: true),允许跨域请求,这在前后端分离项目中非常常见。
接下来,app.setGlobalPrefix('api') 设置了全局路由前缀,所有控制器路由都会以 /api 开头,便于 API 管理。然后,我们启用全局验证管道 ValidationPipe,这是 NestJS 的强大特性之一。管道配置包括:
- whitelist: true:自动过滤掉 DTO 中未定义的属性,确保只处理预期字段。
- forbidNonWhitelisted: true:如果请求中包含未定义属性,直接抛出错误,提高安全性。
- transform: true:自动转换类型,例如将字符串 "1" 转为数字 1,简化数据处理。
最后,监听端口,使用 process.env.PORT ?? 3000 fallback 到默认 3000 端口。这种配置让项目启动简单且灵活。在实际开发中,如果部署到云平台,可以通过环境变量动态设置端口。
根模块与基本服务
NestJS 的模块化设计从 AppModule 开始,它是应用的入口模块,负责导入其他模块、控制器和服务。示例代码如下:
TypeScript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PostsModule } from './posts/posts.module';
import { PrismaModule } from './prisma/prisma.module'
@Module({
// PrismaModule prisma 命令行的方式,client 代表数据库
imports: [PostsModule, PrismaModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
这里,@Module 装饰器定义了模块结构。imports 数组导入了 PostsModule(处理帖子相关逻辑)和 PrismaModule(数据库交互)。controllers 和 providers 分别注册了 AppController 和 AppService。
对应的 AppService 是一个简单的 Injectable 服务:
TypeScript
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
它提供了一个 getHello 方法,返回问候语。这在项目初期用于测试。
AppController 则处理根路由:
TypeScript
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
通过依赖注入,控制器调用服务方法。访问 /api/ 将返回 "Hello World!"。这个结构展示了 NestJS 的 IoC(控制反转)容器如何管理依赖。
代码中无明显错误,但注释提到“PrismaModule prisma 命令行的方式,client 代表数据库”,这提醒我们 Prisma 使用命令行工具生成客户端,确实如此。建议在项目中运行 npx prisma generate 确保客户端更新。
Prisma 的集成与全局模块
Prisma 是 NestJS 的理想数据库伙伴,它提供类型安全的查询和迁移功能。我们通过 PrismaModule 全局注入 PrismaClient。模块代码:
TypeScript
import { Module, Global } from '@nestjs/common'
import { PrismaService } from './prisma.service'
// 全局注入依赖,nestjs 自动处理
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService]
})
export class PrismaModule {
}
@Global() 使模块全局可用,无需在每个模块中重复导入。providers 注册 PrismaService,exports 导出它供其他模块使用。
PrismaService 扩展了 PrismaClient:
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();
}
}
实现 OnModuleInit 接口,在模块初始化时连接数据库。这确保服务启动时数据库就绪。Prisma 的优势在于 schema.prisma 文件定义模型后,自动生成客户端,支持 IntelliSense。
关于迁移(migrate):Prisma 使用 npx prisma migrate dev 命令应用 schema 变更,自动生成 SQL 迁移文件并执行,留下日志便于版本控制。这比手动 SQL 更方便。Seeds 功能通过 npx prisma db seed 插入初始数据,适合测试环境。
代码中,一切正常,但建议添加 onModuleDestroy 钩子断开连接:async onModuleDestroy() { await this.$disconnect(); },防止资源泄漏。
Posts 模块:控制器与服务
Posts 模块处理帖子 API。模块定义:
TypeScript
import { Module } from '@nestjs/common';
import { PostsService } from './posts.service'
import { PostsController } from './posts.controller'
@Module({
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {
}
简单注册控制器和服务。
PostsController 处理 GET 请求:
TypeScript
import {
Controller,
Get,
Query,
} from '@nestjs/common';
import { PostsService } from './posts.service'
import { PostQueryDto } from './dto/post-query.dto'
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {
}
@Get()
async getPosts(@Query() query: PostQueryDto) {
console.log(query)
return this.postsService.findAll(query);
}
}
使用 @Query() 注入查询参数,类型为 PostQueryDto。这会触发验证管道。路由 /api/posts 支持分页查询。
PostsService 实现业务逻辑:
TypeScript
import {
Injectable,
} from '@nestjs/common'
import { PostQueryDto } from './dto/post-query.dto'
import { PrismaService } from '../prisma/prisma.service'
@Injectable()
export class PostsService {
constructor(private prisma: PrismaService) {
}
async findAll(query: PostQueryDto) {
const total = await this.prisma.post.count();
console.log(total, "**************");
return {
items: []
}
}
}
这里注入 PrismaService,计算帖子总数。但返回的 items: [] 是空数组,这是一个明显问题!实际应使用 Prisma 查询数据。优化为:
TypeScript
async findAll(query: PostQueryDto) {
const { page = 1, limit = 10 } = query;
const skip = (page - 1) * limit;
const items = await this.prisma.post.findMany({
skip,
take: limit,
});
const total = await this.prisma.post.count();
return {
items,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
}
};
}
这样实现分页。原代码中的 console.log 用于调试,可在生产中移除。
DTO 与验证器
DTO 是数据传输对象,用于标准化前端到后端的参数传递。PostQueryDto 示例:
TypeScript
import {
IsOptional,
IsInt,
Min
} from 'class-validator'
import { Type } from 'class-transformer'
// 为 transfer object 保驾护航
export class PostQueryDto {
@IsOptional() // 可选的
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
limit?: number = 10
}
使用 class-validator 和 class-transformer 装饰器:
- @IsOptional():参数可选,默认值 1 或 10。
- @Type(() => Number):确保类型转换。
- @IsInt() 和 @Min(1):验证整数且最小为 1。
全局管道会自动应用这些规则。如果参数无效(如 page="abc"),会抛出 400 错误。这规范化了验证流程,避免手动检查。
笔记中提到“从前端 -> 后端 -> 控制器 -> service transfer 过程”,正是 DTO 的作用。它在控制器中注入后,传递到服务,确保数据一致性。对于创建帖子的 PostNewDto,可以类似定义必填字段如 title、content,并添加 @IsString() 等验证。
一个指正:默认值在类中设置,但如果前端未传,管道会使用默认。确保 schema.prisma 中 post 模型定义匹配,如 id、title 等。
数据库交互与最佳实践
PrismaClient 通过命令行生成,替换传统 DB 连接。在服务中注入后,可执行 CRUD 操作。例如,扩展 PostsService 添加创建方法:
TypeScript
async create(dto: PostNewDto) {
return this.prisma.post.create({ data: dto });
}
迁移日志帮助追踪变更,seeds 用于初始化数据,如在 seed.ts 中批量插入测试帖子。
总结
通过 NestJS 与 Prisma 的集成,我们构建了一个模块化、可验证的 API 服务。从 bootstrap 配置到 DTO 验证,再到数据库查询,每一步都体现了框架的优雅。