Files
invest-mind-store/apps/api/NESTJS-GENERATE-GUIDE.md

715 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 可以生成扁平结构(所有文件在同一目录)
- 不推荐使用,会破坏模块化结构
## DTOData Transfer Object详解
### 什么是 DTO
DTOData 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<User> {
// 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<User[]> {
// 使用 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.**为每个操作创建对应的 DTOCreate、Update、Query、Response**
6.**使用 `class-validator` 进行数据验证**
7.**使用 `PartialType` 创建 Update DTO**
8.**启用全局验证管道**
9.**区分 DTO 和 Entity不要混用**
10.**公共组件放在 `common/` 目录**
11.**避免使用扁平结构**
12.**避免按类型组织文件**
13.**避免在 Entity 中直接暴露给 API**