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

16 KiB
Raw Blame History

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 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

方法二:分别生成各个文件

# 生成模块
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

注意事项

  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. 数据验证

// 没有 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,
        });
    }
}

最佳实践

  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