16 KiB
16 KiB
NestJS 模块生成指南
快速生成完整模块
方法一:使用 resource 命令(推荐)
一次性生成包含 module、controller、service、entity 的完整 CRUD 资源:
# 生成 user 模块(会在 src/modules/user 目录下创建所有文件)
pnpm nest g resource modules/user
# 或者使用别名
pnpm nest g res modules/user
交互式选项:
What transport layer do you use?→ 选择REST APIWould 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)
方法二:分别生成各个文件
# 生成模块
pnpm nest g module modules/user
# 生成控制器
pnpm nest g controller modules/user
# 生成服务
pnpm nest g service modules/user
# 生成实体(需要手动创建,或使用 TypeORM CLI)
方法三:使用 TypeORM 生成实体
# 安装 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 模块
# 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:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
user.service.ts:
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:
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);
}
}
常用生成命令速查
# 生成资源(完整 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
注意事项
-
路径规范:
- 使用
modules/模块名作为路径 - 会自动创建目录结构
- 使用
-
自动导入:
- 生成的文件会自动导入到相应的模块中
- 如果不想自动导入,使用
--skip-import选项
-
测试文件:
- 默认会生成
.spec.ts测试文件 - 使用
--no-spec可以跳过测试文件生成
- 默认会生成
-
扁平结构:
- 使用
--flat可以生成扁平结构(所有文件在同一目录) - 不推荐使用,会破坏模块化结构
- 使用
DTO(Data Transfer Object)详解
什么是 DTO?
DTO(Data Transfer Object)是数据传输对象,用于在不同层之间传输数据。在 NestJS 中,DTO 主要用于:
- 定义 API 请求和响应的数据结构
- 数据验证(结合
class-validator) - 类型安全
- API 文档生成(结合 Swagger)
为什么需要 DTO?
1. 数据验证
// 没有 DTO - 不安全
@Post()
create(@Body() body: any) {
// body 可能是任何数据,没有验证
return this.userService.create(body);
}
// 使用 DTO - 安全
@Post()
create(@Body() createUserDto: CreateUserDto) {
// 数据已经验证,类型安全
return this.userService.create(createUserDto);
}
2. 类型安全
// 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 文档:
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(创建数据)
用于创建新资源时的数据验证:
// 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(更新数据)
用于更新资源时的数据验证:
// 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(查询参数)
用于查询列表时的参数验证:
// 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 响应的数据结构:
// 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
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 中启用全局验证:
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);
}
常用验证装饰器
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 示例:完整的用户模块
// 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;
}
// dto/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
// 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;
}
安装必要的依赖
# 安装验证库
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 转换示例
// 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,
});
}
}
最佳实践
- ✅ 使用
resource命令生成完整模块 - ✅ 按功能模块组织文件
- ✅ 每个模块包含:module、controller、service、entity
- ✅ DTO 文件放在模块目录下的
dto/子目录 - ✅ 为每个操作创建对应的 DTO(Create、Update、Query、Response)
- ✅ 使用
class-validator进行数据验证 - ✅ 使用
PartialType创建 Update DTO - ✅ 启用全局验证管道
- ✅ 区分 DTO 和 Entity,不要混用
- ✅ 公共组件放在
common/目录 - ❌ 避免使用扁平结构
- ❌ 避免按类型组织文件
- ❌ 避免在 Entity 中直接暴露给 API