nest简介
Nest是一个用于构建高效、可扩展的Node服务器端应用框架。它默认使用Express作为HTTP服务端框架,但是也可以支持Fastify作为替代,使用TypeScript构建并全面支持 TypeScript,同时仍允许开发者使用纯 JavaScript 编码,融合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程)等元素。它的核心目标是解决传统 Node.js 框架(如 Express/Koa)缺乏统一架构规范、大型项目代码易混乱的问题。
NestJS 采用了一种清晰的三层结构来组织应用逻辑,也就是MVC结构。
1. 模块 (Modules)
-
作用: 模块是 NestJS 应用的基本组织单元 。它们是具有
@Module()装饰器的类,用于将应用的不同功能和特性(如用户管理、订单处理等)逻辑上划分为独立的、可重用的部分。 -
职责: 模块负责组合 控制器和服务(提供者),并定义一组功能。每个应用至少有一个根模块(Root Module) 。
-
关键属性:
controllers: 属于本模块的控制器。providers: 属于本模块的服务 、仓库、工厂等(统称为提供者)。imports: 导入其他模块,以便使用它们导出的提供者。exports: 导出本模块的提供者,供其他模块导入使用。
2. 控制器 (Controllers)
- 作用: 控制器负责处理传入的请求 和返回响应。它们作为应用的入口点。
- 职责: 它们使用
@Controller()装饰器定义路由路径,并使用@Get(),@Post(),@Put(),@Delete()等装饰器来处理特定的 HTTP 方法。控制器通常只包含路由逻辑,并将复杂的业务逻辑委托给服务(提供者)。
3. 提供者 (Providers / Services)
- 作用: 提供者是 NestJS 中最核心 的概念之一,用于处理业务逻辑和数据持久化。
- 职责: 它们是可注入(Injectable)的类,通常包含复杂的逻辑、数据库交互、外部 API 调用等。它们使用
@Injectable()装饰器,并由 NestJS 的依赖注入系统来管理其生命周期和依赖关系。 - 常见类型: 服务(Services)、仓库(Repositories)、工厂(Factories)、辅助函数等。
架构支撑模块(通过装饰器实现)
这些不是传统意义上的"模块类",而是 NestJS 用来增强功能和控制应用行为的架构组件:
| 模块/概念 | 装饰器 | 作用 |
|---|---|---|
| 依赖注入 (DI) | @Injectable() |
管理提供者的生命周期,自动解决组件间的依赖关系,实现松散耦合。 |
| 中间件 (Middleware) | N/A (通过 MiddlewareConsumer) |
在控制器处理请求之前执行的函数,用于身份验证、日志记录等。 |
| 守卫 (Guards) | @Injectable(), @UseGuards() |
在请求进入控制器方法之前执行,用于授权(判断用户是否有权访问此路由)。 |
| 拦截器 (Interceptors) | @Injectable(), @UseInterceptors() |
拦截请求 和响应,用于转换数据、添加缓存、或执行请求/响应日志记录等。 |
| 管道 (Pipes) | @Injectable(), @UsePipes() |
在控制器方法被调用之前执行,用于数据验证(Validation)和数据转换(Transformation)。 |
| 过滤器 (Exception Filters) | @Catch(), @UseFilters() |
捕获并处理应用中未处理的异常,定制化返回给客户端的错误响应格式。 |
nest从发送请求的响应的流程大致是这样的

总结来说,模块 是应用的骨架,控制器 是入口,提供者 是核心业务逻辑,而 守卫、管道、拦截器 等则是 NestJS 提供的强大工具,用于处理请求生命周期中的授权、验证和转换等任务。
以上就是nest的一些简单介绍,想详细了解的可以到nest的官网学习:
使用nest实现一个简单的URUD功能
首先nest有一个自己的cli可以让我们快速创建一个nest项目,因此我们需要先安装一下nest Cli
js
npm i -g @nestjs/cli
安装完nest Cli后,就可以创建项目了
js
nest new [项目名称]
// CLI 会询问你使用哪个包管理器(npm、yarn 或 pnpm),选择后会自动创建项目并安装依赖。
新建完项目后,可以看到我们的项目结构是这样的:
bash
project-name/
├── src/
│ ├── main.ts # 应用入口文件
│ ├── app.module.ts # 根模块
│ ├── app.controller.ts # 根控制器
│ └── app.service.ts # 根服务
├── test/ # 测试文件
├── dist/ # 编译后的文件
├── node_modules/ # 依赖包
├── package.json # 项目配置和依赖
├── tsconfig.json # TypeScript 配置
├── nest-cli.json # NestJS CLI 配置
└── README.md # 项目说明
主要实现一个用户模块,在项目代码中有较为详细的注解说明。
以下是一个简单的User模块的代码,为了方便理解,先用一个示例来简单说它们的大概流程(创建用户):
1. 请求入口
http
POST /api/users
Content-Type: application/json
{
"name": "张三",
"email": "zhangsan@example.com",
"age": 25
}
发送一个POST类型的接口,地址/api/users,下方的请求体是请求体body。
2. 路由匹配
文件: user.controller.ts
js
@Controller('api/users') // 路由前缀
export class UserController {
@Post() // 匹配 POST /api/users
async create(@Body() createUserDto: CreateUserDto) {
return await this.userService.create(createUserDto);
}
}
流程说明:
- NestJS 路由系统匹配
POST /api/users - 找到
UserController的create方法 - 准备执行方法
3. 数据验证(ValidationPipe)
文件:main.ts
typescript
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 过滤未定义的属性
forbidNonWhitelisted: true, // 禁止未定义的属性
transform: true, // 自动转换类型
}),
);
验证流程:
@Body()装饰器提取请求体 JSON 数据- ValidationPipe 根据
CreateUserDto的验证规则进行验证:name: 必须是字符串(@IsString())email: 必须是有效邮箱格式(@IsEmail())age: 可选,如果提供必须是 0-150 的整数(@IsOptional(),@IsInt(),@Min(0),@Max(150))
- 如果验证失败,返回
400 Bad Request,包含错误详情 - 如果验证通过,将数据转换为
CreateUserDto实例
验证规则(CreateUserDto):
typescript
export class CreateUserDto {
@IsString({ message: '用户名必须是字符串' })
name: string;
@IsEmail({}, { message: '邮箱格式不正确' })
email: string;
@IsOptional()
@IsInt({ message: '年龄必须是整数' })
@Min(0, { message: '年龄不能小于0' })
@Max(150, { message: '年龄不能大于150' })
age?: number;
}
4. 控制器处理
文件: user.controller.ts
typescript
async create(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
return await this.userService.create(createUserDto);
}
流程说明:
- 接收验证后的
CreateUserDto对象 - 调用
userService.create()方法 - 等待服务层返回结果
- 返回
UserResponseDto类型的数据
5. 服务层处理
文件: user.service.ts
typescript
async create(createUserDto: CreateUserDto): Promise<User> {
// 1. 创建实体实例
const newUser = this.userRepository.create({
name: createUserDto.name,
email: createUserDto.email,
age: createUserDto.age,
});
// 2. 保存到数据库
return await this.userRepository.save(newUser);
}
流程说明:
-
创建实体实例:
repository.create()创建User实体实例- 此时实体还未保存到数据库
- 只设置了基本字段(name, email, age)
-
保存到数据库:
repository.save()执行 INSERT 操作- TypeORM 自动处理:
- 生成自增 ID(
@PrimaryGeneratedColumn()) - 设置
createdAt(@CreateDateColumn()) - 设置
updatedAt(@UpdateDateColumn())
- 生成自增 ID(
主要涉及的文件main.ts,主要入口,用于项目的入口。
js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
/**
* 应用程序启动入口
* bootstrap 函数是 NestJS 应用的入口点
*/
async function bootstrap() {
// 使用 NestFactory 创建 NestJS 应用实例
// NestFactory 是创建应用的核心工厂类
// AppModule 是应用的根模块,包含了所有功能模块的配置
const app = await NestFactory.create(AppModule);
/**
* 启用跨域资源共享 (CORS)
* 允许前端应用(运行在 localhost:3000)访问后端 API
* credentials: true 表示允许携带认证信息(如 cookies)
*/
app.enableCors({
origin: 'http://localhost:3000', // 允许的前端域名
credentials: true, // 允许携带凭证
});
/**
* 全局验证管道
* 自动验证所有传入的请求数据,根据 DTO 中的装饰器进行验证
*/
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 自动过滤掉 DTO 中未定义的属性
forbidNonWhitelisted: true, // 如果请求包含未定义的属性,抛出错误
transform: true, // 自动将请求数据转换为 DTO 实例
}),
);
/**
* 启动 HTTP 服务器
* process.env.PORT 从环境变量读取端口,如果未设置则默认使用 3001
* ?? 是空值合并运算符,当左侧为 null 或 undefined 时使用右侧的值
*/
await app.listen(process.env.PORT ?? 3001);
console.log(`Application is running on: http://localhost:${process.env.PORT ?? 3001}`);
}
// 执行启动函数
bootstrap();
app.module.ts,模块如果需要在当前模块使用那就需要进行导入。
js
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
/**
* AppModule - 应用的根模块
*
* @Module 装饰器用于定义一个模块类
* 模块是 NestJS 中组织代码的基本单元,类似于 Angular 的模块概念
*
* 模块的三个主要属性:
* - imports: 导入其他模块,使当前模块可以使用其他模块导出的功能
* - controllers: 注册控制器,处理 HTTP 请求
* - providers: 注册提供者(服务),实现业务逻辑,可以在整个模块中注入使用
*/
@Module({
imports: [
/**
* ConfigModule - 配置模块
*
* 作用:管理应用配置,支持从环境变量、配置文件等读取配置
*
* 参数说明:
* - isGlobal: true - 设置为全局模块,所有模块都可以直接使用 ConfigService
* - envFilePath: '.env' - 环境变量文件路径
* - load: [] - 可以加载额外的配置源
*
* 说明:
* - 全局模块意味着不需要在每个模块中导入 ConfigModule
* - 可以直接在任何地方注入 ConfigService 使用
*/
ConfigModule.forRoot({
isGlobal: true, // 全局配置模块
envFilePath: '.env', // 环境变量文件路径
}),
/**
* TypeOrmModule.forRootAsync() - TypeORM 异步配置
*
* 作用:配置 TypeORM 数据库连接
*
* 为什么使用 forRootAsync:
* - 可以异步获取配置(如从 ConfigService)
* - 支持依赖注入(如注入 ConfigService)
* - 更灵活,可以在运行时读取配置
*
* 参数说明:
* - imports: [ConfigModule] - 导入 ConfigModule 以使用 ConfigService
* - useFactory: 工厂函数,返回 TypeORM 配置对象
* - inject: [ConfigService] - 注入 ConfigService 到工厂函数
*/
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
/**
* type - 数据库类型
* 支持:mysql, postgres, sqlite, mssql, mongodb 等
*/
type: 'mysql',
/**
* host - 数据库主机地址
* 从环境变量 DB_HOST 读取,默认 localhost
*/
host: configService.get('DB_HOST', 'localhost'),
/**
* port - 数据库端口
* 从环境变量 DB_PORT 读取,默认 3306(MySQL 默认端口)
*/
port: configService.get<number>('DB_PORT', 3306),
/**
* username - 数据库用户名
* 从环境变量 DB_USERNAME 读取
*/
username: configService.get('DB_USERNAME', 'root'),
/**
* password - 数据库密码
* 从环境变量 DB_PASSWORD 读取
*/
password: configService.get('DB_PASSWORD', ''),
/**
* database - 数据库名称
* 从环境变量 DB_DATABASE 读取
*/
database: configService.get('DB_DATABASE', 'nest_db'),
/**
* entities - 实体类数组
* 指定 TypeORM 要管理的实体类
*/
entities: [__dirname + '/**/*.entity{.ts,.js}'],
/**
* synchronize - 自动同步数据库结构
*
* 作用:根据实体类自动创建或更新数据库表结构
*
* 注意:
* - true: 开发环境使用,自动同步表结构(方便开发)
* - false: 生产环境必须设为 false,使用迁移(migration)管理数据库结构
* - 设为 true 时,修改实体类会自动修改数据库表,可能丢失数据
*/
synchronize: configService.get('DB_SYNCHRONIZE', 'true') === 'true',
/**
* logging - SQL 日志
*
* 作用:是否打印执行的 SQL 语句
*
* 说明:
* - true: 开发环境使用,方便调试,可以看到执行的 SQL
* - false: 生产环境使用,减少日志输出
* - 也可以设置为 ['query', 'error'] 等数组,只记录特定类型的日志
*/
logging: configService.get('DB_LOGGING', 'true') === 'true',
/**
* 其他常用配置选项:
*
* - charset: 'utf8mb4' - 字符集(支持 emoji)
* - timezone: '+08:00' - 时区
* - extra: { connectionLimit: 10 } - 连接池配置
* - migrations: [] - 数据库迁移文件
* - subscribers: [] - 订阅者(实体监听器)
*/
}),
inject: [ConfigService],
}),
UserModule, // 导入用户模块,这样才可以使用user模块中导出的方法
],
controllers: [AppController], // 注册应用控制器
providers: [AppService], // 注册应用服务
})
export class AppModule {}
user.controller.ts,在user模块中的控制器,用于路由的匹配。
js
import {
Controller,Get,Post,Body,Patch,Param,Delete,ParseIntPipe,} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserResponseDto } from './dto/user-response.dto';
/**
* UserController - 用户控制器
*
* 负责处理用户相关的 HTTP 请求
* 实现完整的 CRUD 操作:Create(创建)、Read(读取)、Update(更新)、Delete(删除)
*/
/**
* @Controller('api/users') - 控制器路由装饰器
*
* 作用:定义控制器的路由前缀,所有该控制器下的路由都会自动加上这个前缀
* 参数:'api/users' - 路由前缀路径
* 效果:
* - 如果方法装饰器是 @Get(),完整路径为 GET /api/users
* - 如果方法装饰器是 @Get(':id'),完整路径为 GET /api/users/:id
*/
@Controller('api/users')
export class UserController {
/**
* 依赖注入 - 通过构造函数注入 UserService
* private readonly 表示这是私有只读属性,只能在类内部使用
*/
constructor(private readonly userService: UserService) {}
/**
* 创建用户
* POST /api/users
*/
/**
* @Post() - HTTP POST 方法装饰器
*
* 作用:将方法标记为处理 POST 请求的路由处理器
* 参数:不传参数时,路由路径为空,完整路径为控制器的前缀 + 空路径 = /api/users
* 说明:POST 请求通常用于创建新资源
*/
@Post()
async create(
/**
* @Body() - 请求体参数装饰器
*
* 作用:从 HTTP 请求体中提取数据并注入到方法参数中
* 说明:
* - NestJS 会自动将 JSON 请求体解析为 CreateUserDto 对象
* - 结合 ValidationPipe,会自动验证数据是否符合 DTO 中定义的规则
* - 如果验证失败,会返回 400 错误
*/
@Body() createUserDto: CreateUserDto
): Promise<UserResponseDto> {
return await this.userService.create(createUserDto);
}
/**
* 获取所有用户
* GET /api/users
*/
/**
* @Get() - HTTP GET 方法装饰器
*
* 作用:将方法标记为处理 GET 请求的路由处理器
* 参数:不传参数时,路由路径为空,完整路径为 /api/users
* 说明:GET 请求用于获取资源
*/
@Get()
async findAll(): Promise<UserResponseDto[]> {
return await this.userService.findAll();
}
/**
* 根据ID获取单个用户
* GET /api/users/:id
*/
/**
* @Get(':id') - HTTP GET 方法装饰器(带路径参数)
*
* 作用:定义带路径参数的 GET 路由
* 参数:':id' - 路径参数,冒号表示这是一个动态参数
* 完整路径:GET /api/users/:id
* 说明:路径参数可以通过 @Param() 装饰器获取
*/
@Get(':id')
async findOne(
/**
* @Param('id', ParseIntPipe) - 路径参数装饰器 + 管道转换
*
* 作用:
* 1. @Param('id') - 从路由路径中提取名为 'id' 的参数
* 2. ParseIntPipe - 将字符串参数转换为整数类型
*
* 说明:
* - HTTP 请求中的路径参数默认是字符串类型
* - ParseIntPipe 会自动将字符串转换为数字
* - 如果转换失败(如传入非数字),会自动返回 400 错误
* - 这是 NestJS 中类型转换和验证的常用方式
*/
@Param('id', ParseIntPipe) id: number
): Promise<UserResponseDto> {
return await this.userService.findOne(id);
}
/**
* 更新用户信息
* PATCH /api/users/:id
*/
/**
* @Patch(':id') - HTTP PATCH 方法装饰器
*
* 作用:将方法标记为处理 PATCH 请求的路由处理器
* 参数:':id' - 路径参数,表示要更新的资源ID
* 说明:
* - PATCH 用于部分更新资源(只更新提供的字段)
* - 与 PUT 的区别:PUT 通常用于完整替换资源,PATCH 用于部分更新
* - RESTful API 中,更新操作通常使用 PATCH
*/
@Patch(':id')
async update(
/**
* @Param('id', ParseIntPipe) - 提取并转换路径参数 id
*/
@Param('id', ParseIntPipe) id: number,
/**
* @Body() - 提取请求体数据
*
* 说明:UpdateUserDto 中的字段都是可选的,可以只更新部分字段
*/
@Body() updateUserDto: UpdateUserDto,
): Promise<UserResponseDto> {
return await this.userService.update(id, updateUserDto);
}
/**
* 删除用户
* DELETE /api/users/:id
*/
/**
* @Delete(':id') - HTTP DELETE 方法装饰器
*
* 作用:将方法标记为处理 DELETE 请求的路由处理器
* 参数:':id' - 路径参数,表示要删除的资源ID
* 说明:DELETE 请求用于删除资源,是 RESTful API 中删除操作的标准方法
*/
@Delete(':id')
async remove(
/**
* @Param('id', ParseIntPipe) - 提取并转换路径参数 id
*/
@Param('id', ParseIntPipe) id: number
): Promise<void> {
return await this.userService.remove(id);
}
}
user.service.ts,在user模块中的提供者,用于处理请求的业务逻辑。
js
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
/**
* UserService - 用户服务类
*
* 负责处理用户相关的业务逻辑
* 使用 TypeORM Repository 进行数据库操作
*/
/**
* @Injectable() - 可注入装饰器
*
* 作用:标记此类可以被 NestJS 的依赖注入系统管理
*
* 功能说明:
* 1. 使此类可以被其他类(如控制器)通过构造函数注入使用
* 2. NestJS 会自动管理其生命周期(默认是单例模式,整个应用只有一个实例)
* 3. 可以在模块的 providers 数组中注册
* 4. 支持依赖注入,可以在构造函数中注入其他服务
*
* 生命周期:
* - 默认作用域:单例(SINGLETON),应用启动时创建一次,所有地方共享同一个实例
* - 可以通过 { scope: Scope.REQUEST } 设置为请求作用域,每个请求创建新实例
* - 可以通过 { scope: Scope.TRANSIENT } 设置为瞬态作用域,每次注入都创建新实例
*
* 使用场景:
* - 封装业务逻辑
* - 数据访问层(如数据库操作)
* - 可复用的功能模块
*/
@Injectable()
export class UserService {
/**
* 构造函数 - 依赖注入 Repository
*
* @InjectRepository(User) - Repository 注入装饰器
*
* 作用:注入 TypeORM Repository,用于数据库操作
*
* 参数说明:
* - User - 实体类,指定要操作的实体类型
*
* 说明:
* - Repository<User> 是 TypeORM 提供的数据库操作接口
* - 提供了丰富的数据库操作方法(find, save, delete, update 等)
* - 类型安全:TypeScript 会根据 User 实体类提供类型提示
* - 需要在 UserModule 中使用 TypeOrmModule.forFeature([User]) 注册
*/
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
/**
* 创建用户
*
* @param createUserDto 创建用户的数据
* @returns 创建的用户对象(Promise)
*
* 说明:
* - create() 方法创建实体实例,但不保存到数据库
* - save() 方法将实体保存到数据库
* - TypeORM 会自动处理 createdAt 和 updatedAt 字段
*/
async create(createUserDto: CreateUserDto): Promise<User> {
// 创建实体实例
const newUser = this.userRepository.create({
name: createUserDto.name,
email: createUserDto.email,
age: createUserDto.age,
});
// 保存到数据库
// save() 方法会:
// 1. 插入新记录到数据库
// 2. 自动设置 createdAt 和 updatedAt(因为使用了 @CreateDateColumn 和 @UpdateDateColumn)
// 3. 返回保存后的实体(包含自动生成的 id)
return await this.userRepository.save(newUser);
}
/**
* 获取所有用户
*
* @returns 用户数组(Promise)
*
* 说明:
* - find() 方法查询所有记录
* - 返回数组,如果没有记录则返回空数组 []
*/
async findAll(): Promise<User[]> {
return await this.userRepository.find();
}
/**
* 根据ID获取单个用户
*
* @param id 用户ID
* @returns 用户对象(Promise)
* @throws NotFoundException 如果用户不存在
*
* 说明:
* - findOne({ where: { id } }) 根据条件查询单条记录
* - 如果找不到记录,返回 null
* - 需要手动检查并抛出异常
*/
async findOne(id: number): Promise<User> {
const user = await this.userRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException(`用户 ID ${id} 不存在`);
}
return user;
}
/**
* 更新用户信息
*
* @param id 用户ID
* @param updateUserDto 更新的数据
* @returns 更新后的用户对象(Promise)
* @throws NotFoundException 如果用户不存在
*
* 说明:
* - 先查找用户,如果不存在则抛出异常
* - 使用 Object.assign() 或展开运算符更新字段
* - save() 方法会:
* 1. 如果实体有 id,执行 UPDATE 操作
* 2. 自动更新 updatedAt 字段(因为使用了 @UpdateDateColumn)
* 3. 返回更新后的实体
*/
async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
// 查找用户,如果不存在会抛出异常
const user = await this.findOne(id);
// 更新用户信息
// Object.assign() 将 updateUserDto 中的属性复制到 user 对象
// 只更新提供的字段,未提供的字段保持不变
Object.assign(user, updateUserDto);
// 保存更新
// save() 方法检测到实体有 id,会执行 UPDATE 而不是 INSERT
// @UpdateDateColumn 装饰的字段会自动更新为当前时间
return await this.userRepository.save(user);
}
/**
* 删除用户
*
* @param id 用户ID
* @throws NotFoundException 如果用户不存在
*
* 说明:
* - remove() 方法需要传入实体对象
* - delete() 方法可以直接传入 id,更高效
* - 两种方式都可以,delete() 更简单直接
*/
async remove(id: number): Promise<void> {
// 先检查用户是否存在
await this.findOne(id); // 如果不存在会抛出异常
// 删除用户
// delete() 方法根据 id 删除记录
// 返回 DeleteResult 对象,包含 affected 属性(受影响的行数)
await this.userRepository.delete(id);
}
}
以上涉及的DTO有三个,代码如下:
create-user.dto.ts:
js
import { IsString, IsEmail, IsOptional, IsInt, Min, Max } from 'class-validator';
/**
* CreateUserDto - 创建用户数据传输对象
* 用于接收创建用户的请求数据
*
* DTO (Data Transfer Object) 的作用:
* 1. 定义 API 接口的请求数据结构
* 2. 提供数据验证规则(通过 class-validator 装饰器)
* 3. 类型安全:TypeScript 类型检查
*/
export class CreateUserDto {
/**
* @IsString() - 字符串验证装饰器
*
* 作用:验证该字段必须是字符串类型
*
* 参数说明:
* - message: 验证失败时返回的错误消息
*
* 验证规则:
* - 如果传入的值不是字符串,验证会失败
* - 验证失败时,NestJS 会返回 400 错误,错误信息为 "用户名必须是字符串"
*
* 说明:
* - class-validator 提供了丰富的验证装饰器
* - 需要配合 ValidationPipe 使用(在 main.ts 中已配置)
* - 验证在请求到达控制器方法之前自动执行
*/
@IsString({ message: '用户名必须是字符串' })
name: string;
/**
* @IsEmail() - 邮箱格式验证装饰器
*
* 作用:验证该字段必须是有效的邮箱格式
*
* 参数说明:
* - 第一个参数 {}: 邮箱验证选项(使用默认选项)
* - 第二个参数 { message: ... }: 验证失败时的错误消息
*
* 验证规则:
* - 必须符合邮箱格式(如:user@example.com)
* - 验证失败时返回 "邮箱格式不正确"
*
* 说明:
* - 这是数据验证的重要环节,防止无效数据进入系统
* - 验证在服务层处理之前完成,提高系统安全性
*/
@IsEmail({}, { message: '邮箱格式不正确' })
email: string;
/**
* @IsOptional() - 可选字段装饰器
*
* 作用:标记该字段是可选的,如果未提供值,跳过后续验证
*
* 说明:
* - 如果字段存在,则执行后续的验证规则
* - 如果字段不存在(undefined),则跳过验证
* - 必须放在其他验证装饰器之前
* - 与 TypeScript 的可选属性(?)配合使用
*/
@IsOptional()
/**
* @IsInt() - 整数验证装饰器
*
* 作用:验证该字段必须是整数
*
* 说明:
* - 验证值是否为整数类型
* - 如果传入小数或非数字,验证会失败
*/
@IsInt({ message: '年龄必须是整数' })
/**
* @Min() - 最小值验证装饰器
*
* 作用:验证数值必须大于或等于指定值
*
* 参数:0 - 最小值
* 说明:年龄不能为负数
*/
@Min(0, { message: '年龄不能小于0' })
/**
* @Max() - 最大值验证装饰器
*
* 作用:验证数值必须小于或等于指定值
*
* 参数:200 - 最大值
* 说明:年龄不能超过 200
*
* 验证链说明:
* 1. @IsOptional() - 如果字段不存在,跳过后续验证
* 2. @IsInt() - 如果字段存在,必须是整数
* 3. @Min(0) - 如果字段存在且是整数,必须 >= 0
* 4. @Max(150) - 如果字段存在且是整数,必须 <= 150
*
* 验证顺序:从上到下依次执行,任何一个验证失败都会返回错误
*/
@Max(200, { message: '年龄不能大于200' })
age?: number;
}
update-user.dto.ts:
js
import { IsString, IsEmail, IsOptional, IsInt, Min, Max } from 'class-validator';
/**
* UpdateUserDto - 更新用户数据传输对象
* 用于接收更新用户的请求数据
* 所有字段都是可选的,因为更新时可能只更新部分字段
*
* 与 CreateUserDto 的区别:
* - CreateUserDto: 所有必填字段都不能为空(name、email 必填)
* - UpdateUserDto: 所有字段都是可选的,符合 PATCH 部分更新的语义
*
* 设计原则:
* - 部分更新:用户可以只更新需要修改的字段
* - 灵活性:不需要提供所有字段,只提供要更新的字段即可
* - 验证:如果提供了字段,则必须符合验证规则
*/
export class UpdateUserDto {
/**
* @IsOptional() - 标记为可选字段
*
* 说明:更新操作中,如果客户端不提供此字段,表示不更新该字段
*/
@IsOptional()
/**
* @IsString() - 如果提供了 name 字段,则必须是字符串
*
* 验证逻辑:
* - 如果 name 未提供(undefined):跳过验证(因为 @IsOptional)
* - 如果 name 提供了:必须是字符串类型
*/
@IsString({ message: '用户名必须是字符串' })
name?: string;
/**
* @IsOptional() - 标记为可选
*/
@IsOptional()
/**
* @IsEmail() - 如果提供了 email 字段,则必须是有效邮箱格式
*
* 说明:更新邮箱时,必须提供有效的邮箱格式
*/
@IsEmail({}, { message: '邮箱格式不正确' })
email?: string;
/**
* @IsOptional() - 标记为可选
*/
@IsOptional()
/**
* @IsInt() - 如果提供了 age 字段,则必须是整数
*/
@IsInt({ message: '年龄必须是整数' })
/**
* @Min(0) - 如果提供了 age 字段,则必须 >= 0
*/
@Min(0, { message: '年龄不能小于0' })
/**
* @Max(200) - 如果提供了 age 字段,则必须 <= 200
*
* 验证链说明(与 CreateUserDto 相同):
* 1. @IsOptional() - 字段可选
* 2. 如果提供了值,则必须通过后续所有验证
* 3. 验证顺序:IsInt -> Min -> Max
*/
@Max(200, { message: '年龄不能大于200' })
age?: number;
}
user-response.dto.ts :
js
/**
* UserResponseDto - 用户响应数据传输对象
* 用于 API 响应,包含完整的用户信息
*
* 作用:
* 1. 定义 API 响应的数据结构
* 2. 类型安全:确保响应数据的类型正确
*
* 与 User 实体的区别:
* - User 实体:内部数据结构,可能包含敏感信息或内部字段
* - UserResponseDto:对外暴露的数据结构,只包含需要返回给客户端的字段
*
* 最佳实践:
* - 使用 DTO 控制返回给客户端的数据
* - 可以隐藏敏感信息(如密码)
* - 可以格式化数据(如日期格式)
* - 可以添加额外的计算字段
*/
export class UserResponseDto {
id: number;
name: string;
email: string;
age?: number;
createdAt: Date;
updatedAt: Date;
}
user.entity.ts是用户实体类(数据库表映射)。
js
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
/**
* User 实体类 - TypeORM 实体
*
* 作用:
* 1. 定义数据库表结构
* 2. 映射数据库表到 TypeScript 类
* 3. 提供类型安全的数据库操作
*
* @Entity('users') - 实体装饰器
*
* 作用:标记这是一个 TypeORM 实体类
* 参数:'users' - 数据库表名(如果不指定,默认使用类名的小写形式)
*
* 说明:
* - TypeORM 会根据这个实体类自动创建或同步数据库表
* - 实体类中的属性会映射到数据库表的列
*/
@Entity('users')
export class User {
/**
* @PrimaryGeneratedColumn() - 主键自增列装饰器
*
* 作用:定义主键列,自动生成递增的 ID
*
* 说明:
* - 这是数据库表的主键
* - 自动递增,每次插入新记录时自动生成
* - 类型为 number,对应 MySQL 的 INT 或 BIGINT
*/
@PrimaryGeneratedColumn()
id: number;
/**
* @Column() - 普通列装饰器
*
* 作用:定义数据库表的普通列
*
* 参数说明:
* - type: 'varchar' - 数据库列类型,varchar 是可变长度字符串
* - length: 255 - 最大长度
* - nullable: false - 不允许为空(必填字段)
*
* 说明:
* - 如果不指定 type,TypeORM 会根据 TypeScript 类型推断
* - string 类型默认映射为 varchar(255)
*/
@Column({ type: 'varchar', length: 255, nullable: false })
name: string;
/**
* @Column() - 邮箱列
*
* 参数说明:
* - unique: true - 唯一约束,确保邮箱不重复
* - nullable: false - 不允许为空
*
* 说明:
* - unique: true 会在数据库层面创建唯一索引
* - 如果尝试插入重复邮箱,数据库会抛出错误
*/
@Column({ type: 'varchar', length: 255, unique: true, nullable: false })
email: string;
/**
* @Column() - 年龄列(可选字段)
*
* 参数说明:
* - type: 'int' - 整数类型
* - nullable: true - 允许为空(可选字段)
* - default: null - 默认值为 null
*
* 说明:
* - 可选字段在 TypeScript 中使用 ? 标记
* - 数据库列也设置为 nullable: true
*/
@Column({ type: 'int', nullable: true, default: null })
age?: number;
/**
* @CreateDateColumn() - 创建时间列装饰器
*
* 作用:自动管理记录的创建时间
*
* 说明:
* - TypeORM 会在插入新记录时自动设置当前时间
* - 类型为 Date,对应 MySQL 的 DATETIME 或 TIMESTAMP
* - 不需要手动设置,TypeORM 会自动处理
*/
@CreateDateColumn({ type: 'timestamp' })
createdAt: Date;
/**
* @UpdateDateColumn() - 更新时间列装饰器
*
* 作用:自动管理记录的更新时间
*
* 说明:
* - TypeORM 会在更新记录时自动更新为当前时间
* - 每次执行 UPDATE 操作时,此字段会自动更新
* - 不需要手动设置,TypeORM 会自动处理
*/
@UpdateDateColumn({ type: 'timestamp' })
updatedAt: Date;
}
user.module.ts,user的模块,导入其他模块的提供者,或者导出自己的提供者给其他的模块导入。
js
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from './entities/user.entity';
/**
* UserModule - 用户模块
*
* 封装用户相关的所有功能:
* - UserController: 处理用户相关的 HTTP 请求
* - UserService: 处理用户相关的业务逻辑
* - User Entity: 用户实体类(数据库表映射)
*/
/**
* @Module() - 模块装饰器
*
* 作用:定义一个 NestJS 模块,是组织代码的基本单元
*
* 参数说明:
* - controllers: 注册该模块中的控制器
* * 作用:使控制器可以处理 HTTP 请求
* * 说明:只有在此数组中注册的控制器才能被 NestJS 识别和处理
*
* - providers: 注册该模块中的提供者(服务、工厂等)
* * 作用:使服务可以被依赖注入系统管理
* * 说明:只有在此数组中注册的服务才能被注入使用
* * 注意:服务默认是单例的,整个应用共享一个实例
*
* - exports: 导出该模块中的提供者
* * 作用:使其他模块可以导入并使用这些提供者
* * 说明:
* - 只有导出的服务才能被其他模块使用
* - 如果 UserService 被导出,其他模块导入 UserModule 后就可以使用 UserService
* - 这是模块间共享功能的标准方式
*
* - imports: 导入其他模块
* * 作用:使当前模块可以使用其他模块导出的功能
* * 说明:例如导入 TypeOrmModule.forFeature() 可以使用数据库 Repository
*
* 模块的作用:
* 1. 代码组织:将相关功能组织在一起
* 2. 依赖管理:管理模块内部的依赖关系
* 3. 封装:隐藏内部实现,只暴露必要的接口
* 4. 可复用:导出的服务可以在多个模块中复用
*
* 模块的层次结构:
* - 根模块(AppModule):应用的入口模块
* - 功能模块(如 UserModule):封装特定功能的模块
* - 共享模块:提供通用功能的模块
*/
@Module({
/**
* TypeOrmModule.forFeature([User]) - 注册实体到 TypeORM
*
* 作用:在当前模块中注册 TypeORM 实体,使该模块可以使用 Repository
*
* 参数说明:
* - [User] - 实体类数组,指定要在该模块中使用的实体
*
* 说明:
* - forFeature() 必须在每个需要使用 Repository 的模块中调用
* - 注册后,可以在该模块的服务中注入 Repository<User>
* - 与 forRoot() 的区别:
* * forRoot(): 在根模块中配置数据库连接(全局配置)
* * forFeature(): 在功能模块中注册实体(模块级配置)
*
* 使用示例:
* // user.service.ts
* constructor(
* @InjectRepository(User)
* private userRepository: Repository<User>,
* ) {}
*/
imports: [TypeOrmModule.forFeature([User])],
/**
* controllers - 控制器数组
*
* 作用:注册该模块中的控制器
* 说明:UserController 会被注册,可以处理 /api/users 相关的请求
*/
controllers: [UserController],
/**
* providers - 提供者数组
*
* 作用:注册该模块中的服务
* 说明:UserService 会被注册,可以在 UserController 中注入使用
*/
providers: [UserService],
/**
* exports - 导出数组
*
* 作用:导出 UserService,使其他模块可以导入 UserModule 并使用 UserService
* 使用场景:
* - 如果其他模块(如 OrderModule)需要使用 UserService
* - 可以在 OrderModule 中导入 UserModule
* - 然后在 OrderService 中注入 UserService
*
* 示例:
* // order.module.ts
* @Module({
* imports: [UserModule], // 导入 UserModule
* ...
* })
*
* // order.service.ts
* constructor(private userService: UserService) {} // 可以注入使用
*/
exports: [UserService],
})
export class UserModule {}
总结
以上是 Nest.js 的基础介绍及简易 CRUD 实现,这是我入门 Nest.js 的第一步,也是个人学习记录。
想要深入学习,可优先查阅官方文档,也可参考其他博主的文章与视频。
不必纠结 "学 Nest.js 为何不直接学 Java/Go":作为前端开发者,我学习 Nest.js 的成本更低,且现阶段并非为了工作业务应用,更多是方便自己偶尔编写一些个人小Demo。