# NestJS 模块生成指南 ## 快速生成完整模块 ### 方法一:使用 `resource` 命令(推荐) 一次性生成包含 module、controller、service、entity 的完整 CRUD 资源: ```bash # 生成 user 模块(会在 src/modules/user 目录下创建所有文件) pnpm nest g resource modules/user # 或者使用别名 pnpm nest g res modules/user ``` **交互式选项:** - `What transport layer do you use?` → 选择 `REST API` - `Would you like to generate CRUD entry points?` → 选择 `Yes` **生成的文件结构:** ``` src/modules/user/ ├── user.controller.ts # 控制器 ├── user.controller.spec.ts # 控制器测试 ├── user.service.ts # 服务 ├── user.service.spec.ts # 服务测试 ├── user.module.ts # 模块 └── entities/ └── user.entity.ts # 实体(TypeORM) ``` ### 方法二:分别生成各个文件 ```bash # 生成模块 pnpm nest g module modules/user # 生成控制器 pnpm nest g controller modules/user # 生成服务 pnpm nest g service modules/user # 生成实体(需要手动创建,或使用 TypeORM CLI) ``` ### 方法三:使用 TypeORM 生成实体 ```bash # 安装 TypeORM CLI(如果还没有) pnpm add -D typeorm # 生成实体(需要先配置 TypeORM) pnpm typeorm entity:create -n User ``` ## 文件组织模式 ### 模式一:按功能模块组织(推荐)⭐ ``` src/ ├── modules/ │ ├── user/ │ │ ├── user.module.ts │ │ ├── user.controller.ts │ │ ├── user.service.ts │ │ ├── user.entity.ts │ │ ├── user.dto.ts # DTO 文件 │ │ ├── user.controller.spec.ts │ │ └── user.service.spec.ts │ ├── order/ │ │ ├── order.module.ts │ │ ├── order.controller.ts │ │ ├── order.service.ts │ │ ├── order.entity.ts │ │ └── ... │ └── product/ │ └── ... ├── database/ │ ├── database.module.ts │ └── database.config.ts └── app.module.ts ``` **优点:** - 模块化清晰,每个功能独立 - 易于维护和扩展 - 符合 NestJS 最佳实践 **适用场景:** - 大型企业级应用 - 需要复杂业务逻辑 - 需要清晰的架构分层 ## 推荐的文件组织(当前项目) 基于你的项目结构,推荐使用**模式一**: ``` src/ ├── modules/ # 业务模块 │ ├── user/ │ │ ├── user.module.ts │ │ ├── user.controller.ts │ │ ├── user.service.ts │ │ ├── user.entity.ts │ │ ├── dto/ # DTO 文件(可选) │ │ │ ├── create-user.dto.ts │ │ │ └── update-user.dto.ts │ │ └── interfaces/ # 接口定义(可选) │ │ └── user.interface.ts │ ├── order/ │ └── product/ ├── database/ # 数据库配置 │ ├── database.module.ts │ └── database.config.ts ├── common/ # 公共模块(可选) │ ├── filters/ │ ├── guards/ │ ├── interceptors/ │ └── pipes/ └── app.module.ts ``` ## 实际生成示例 ### 生成 user 模块 ```bash # 1. 生成完整资源 cd /Users/joey-xd/sites/vest-mind/vest-mind-backend/apps/api pnpm nest g resource modules/user # 2. 选择选项: # - REST API # - Yes (生成 CRUD) ``` ### 生成后的文件内容示例 **user.entity.ts:** ```typescript import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; } ``` **user.service.ts:** ```typescript import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Injectable() export class UserService { create(createUserDto: CreateUserDto) { return 'This action adds a new user'; } findAll() { return `This action returns all user`; } findOne(id: number) { return `This action returns a #${id} user`; } update(id: number, updateUserDto: UpdateUserDto) { return `This action updates a #${id} user`; } remove(id: number) { return `This action removes a #${id} user`; } } ``` **user.controller.ts:** ```typescript import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { UserService } from './user.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Controller('user') export class UserController { constructor(private readonly userService: UserService) {} @Post() create(@Body() createUserDto: CreateUserDto) { 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: UpdateUserDto) { return this.userService.update(+id, updateUserDto); } @Delete(':id') remove(@Param('id') id: string) { return this.userService.remove(+id); } } ``` ## 常用生成命令速查 ```bash # 生成资源(完整 CRUD) pnpm nest g resource modules/user # 生成模块 pnpm nest g module modules/user # 生成控制器 pnpm nest g controller modules/user # 生成服务 pnpm nest g service modules/user # 生成守卫 pnpm nest g guard modules/user/guards/auth # 生成拦截器 pnpm nest g interceptor modules/user/interceptors/logging # 生成过滤器 pnpm nest g filter modules/user/filters/http-exception # 生成管道 pnpm nest g pipe modules/user/pipes/validation # 生成装饰器 pnpm nest g decorator modules/user/decorators/roles ``` ## 注意事项 1. **路径规范:** - 使用 `modules/模块名` 作为路径 - 会自动创建目录结构 2. **自动导入:** - 生成的文件会自动导入到相应的模块中 - 如果不想自动导入,使用 `--skip-import` 选项 3. **测试文件:** - 默认会生成 `.spec.ts` 测试文件 - 使用 `--no-spec` 可以跳过测试文件生成 4. **扁平结构:** - 使用 `--flat` 可以生成扁平结构(所有文件在同一目录) - 不推荐使用,会破坏模块化结构 ## DTO(Data Transfer Object)详解 ### 什么是 DTO? DTO(Data Transfer Object)是数据传输对象,用于在不同层之间传输数据。在 NestJS 中,DTO 主要用于: 1. **定义 API 请求和响应的数据结构** 2. **数据验证**(结合 `class-validator`) 3. **类型安全** 4. **API 文档生成**(结合 Swagger) ### 为什么需要 DTO? #### 1. 数据验证 ```typescript // 没有 DTO - 不安全 @Post() create(@Body() body: any) { // body 可能是任何数据,没有验证 return this.userService.create(body); } // 使用 DTO - 安全 @Post() create(@Body() createUserDto: CreateUserDto) { // 数据已经验证,类型安全 return this.userService.create(createUserDto); } ``` #### 2. 类型安全 ```typescript // DTO 定义了明确的数据结构 export class CreateUserDto { username: string; email: string; age: number; } // TypeScript 会检查类型 const user = new CreateUserDto(); user.username = 'john'; // ✅ 正确 user.age = '25'; // ❌ TypeScript 错误 ``` #### 3. API 文档 使用 Swagger 时,DTO 会自动生成 API 文档: ```typescript import { ApiProperty } from '@nestjs/swagger'; export class CreateUserDto { @ApiProperty({ description: '用户名', example: 'john' }) username: string; @ApiProperty({ description: '邮箱', example: 'john@example.com' }) email: string; } ``` ### DTO 的类型 #### 1. Create DTO(创建数据) 用于创建新资源时的数据验证: ```typescript // dto/create-user.dto.ts import { IsString, IsEmail, IsOptional, MinLength, MaxLength, } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class CreateUserDto { @ApiProperty({ description: '用户名', example: 'john' }) @IsString() @MinLength(3) @MaxLength(20) username: string; @ApiProperty({ description: '邮箱', example: 'john@example.com' }) @IsEmail() email: string; @ApiProperty({ description: '密码', example: 'password123' }) @IsString() @MinLength(6) password: string; @ApiProperty({ description: '年龄', example: 25, required: false }) @IsOptional() @IsNumber() age?: number; } ``` #### 2. Update DTO(更新数据) 用于更新资源时的数据验证: ```typescript // dto/update-user.dto.ts import { PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from './create-user.dto'; // 方式一:使用 PartialType(推荐) export class UpdateUserDto extends PartialType(CreateUserDto) {} // 方式二:手动定义(更灵活) // export class UpdateUserDto { // @IsOptional() // @IsString() // username?: string; // // @IsOptional() // @IsEmail() // email?: string; // } ``` #### 3. Query DTO(查询参数) 用于查询列表时的参数验证: ```typescript // dto/query-user.dto.ts import { IsOptional, IsNumber, Min, Max } from 'class-validator'; import { Type } from 'class-transformer'; export class QueryUserDto { @IsOptional() @Type(() => Number) @IsNumber() @Min(1) page?: number = 1; @IsOptional() @Type(() => Number) @IsNumber() @Min(1) @Max(100) limit?: number = 10; @IsOptional() @IsString() search?: string; @IsOptional() @IsString() sortBy?: string = 'createdAt'; @IsOptional() @IsString() sortOrder?: 'ASC' | 'DESC' = 'DESC'; } ``` #### 4. Response DTO(响应数据) 用于定义 API 响应的数据结构: ```typescript // dto/user-response.dto.ts import { ApiProperty } from '@nestjs/swagger'; export class UserResponseDto { @ApiProperty({ description: '用户ID', example: 1 }) id: number; @ApiProperty({ description: '用户名', example: 'john' }) username: string; @ApiProperty({ description: '邮箱', example: 'john@example.com' }) email: string; @ApiProperty({ description: '创建时间', example: '2024-01-01T00:00:00Z' }) createdAt: Date; @ApiProperty({ description: '更新时间', example: '2024-01-01T00:00:00Z' }) updatedAt: Date; } ``` ### 在 Controller 中使用 DTO ```typescript import { Controller, Get, Post, Body, Patch, Param, Query, } from '@nestjs/common'; import { UserService } from './user.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { QueryUserDto } from './dto/query-user.dto'; @Controller('users') export class UserController { constructor(private readonly userService: UserService) {} @Post() create(@Body() createUserDto: CreateUserDto) { return this.userService.create(createUserDto); } @Get() findAll(@Query() queryUserDto: QueryUserDto) { return this.userService.findAll(queryUserDto); } @Get(':id') findOne(@Param('id') id: string) { return this.userService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return this.userService.update(+id, updateUserDto); } } ``` ### 启用全局验证管道 在 `main.ts` 中启用全局验证: ```typescript import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 启用全局验证管道 app.useGlobalPipes( new ValidationPipe({ whitelist: true, // 自动删除不在 DTO 中的属性 forbidNonWhitelisted: true, // 如果请求包含未定义的属性,抛出错误 transform: true, // 自动转换类型(如字符串转数字) transformOptions: { enableImplicitConversion: true, }, }), ); await app.listen(3000); } ``` ### 常用验证装饰器 ```typescript import { IsString, // 字符串 IsNumber, // 数字 IsBoolean, // 布尔值 IsEmail, // 邮箱 IsUrl, // URL IsDate, // 日期 IsOptional, // 可选字段 IsNotEmpty, // 非空 MinLength, // 最小长度 MaxLength, // 最大长度 Min, // 最小值 Max, // 最大值 IsEnum, // 枚举 IsArray, // 数组 IsObject, // 对象 ValidateNested, // 嵌套对象验证 IsUUID, // UUID Matches, // 正则匹配 } from 'class-validator'; ``` ### DTO 示例:完整的用户模块 ```typescript // dto/create-user.dto.ts import { IsString, IsEmail, IsOptional, MinLength, IsNumber, Min, Max, } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class CreateUserDto { @ApiProperty({ description: '用户名', example: 'john' }) @IsString() @MinLength(3) @MaxLength(20) username: string; @ApiProperty({ description: '邮箱', example: 'john@example.com' }) @IsEmail() email: string; @ApiProperty({ description: '密码', example: 'password123' }) @IsString() @MinLength(6) password: string; @ApiProperty({ description: '年龄', example: 25, required: false }) @IsOptional() @IsNumber() @Min(18) @Max(100) age?: number; } ``` ```typescript // dto/update-user.dto.ts import { PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from './create-user.dto'; export class UpdateUserDto extends PartialType(CreateUserDto) {} ``` ```typescript // dto/query-user.dto.ts import { IsOptional, IsNumber, IsString, Min, Max } from 'class-validator'; import { Type } from 'class-transformer'; export class QueryUserDto { @IsOptional() @Type(() => Number) @IsNumber() @Min(1) page?: number = 1; @IsOptional() @Type(() => Number) @IsNumber() @Min(1) @Max(100) limit?: number = 10; @IsOptional() @IsString() search?: string; } ``` ### 安装必要的依赖 ```bash # 安装验证库 pnpm add class-validator class-transformer # 如果使用 Swagger(可选) pnpm add @nestjs/swagger swagger-ui-express ``` ### DTO vs Entity | 特性 | DTO | Entity | | -------- | ---------------------- | ------------------- | | **用途** | 数据传输和验证 | 数据库模型 | | **位置** | `dto/` 目录 | `entities/` 目录 | | **验证** | 使用 `class-validator` | 使用 TypeORM 装饰器 | | **暴露** | 暴露给 API 客户端 | 不直接暴露 | | **示例** | `CreateUserDto` | `User` entity | **最佳实践:** - ✅ Entity 包含数据库字段(如 `id`, `createdAt`) - ✅ DTO 只包含客户端需要传递的字段 - ✅ 使用 DTO 转换 Entity,避免暴露敏感信息 ### DTO 转换示例 ```typescript // service 中使用 @Injectable() export class UserService { async create(createUserDto: CreateUserDto): Promise { // DTO → Entity const user = this.userRepository.create({ username: createUserDto.username, email: createUserDto.email, // 不直接传递 password,需要加密 passwordHash: await this.hashPassword(createUserDto.password), }); return this.userRepository.save(user); } async findAll(queryDto: QueryUserDto): Promise { // 使用 DTO 中的查询参数 return this.userRepository.find({ skip: (queryDto.page - 1) * queryDto.limit, take: queryDto.limit, }); } } ``` ## 最佳实践 1. ✅ **使用 `resource` 命令生成完整模块** 2. ✅ **按功能模块组织文件** 3. ✅ **每个模块包含:module、controller、service、entity** 4. ✅ **DTO 文件放在模块目录下的 `dto/` 子目录** 5. ✅ **为每个操作创建对应的 DTO(Create、Update、Query、Response)** 6. ✅ **使用 `class-validator` 进行数据验证** 7. ✅ **使用 `PartialType` 创建 Update DTO** 8. ✅ **启用全局验证管道** 9. ✅ **区分 DTO 和 Entity,不要混用** 10. ✅ **公共组件放在 `common/` 目录** 11. ❌ **避免使用扁平结构** 12. ❌ **避免按类型组织文件** 13. ❌ **避免在 Entity 中直接暴露给 API**