首页
壁纸
统计
友链
Search
1
Nestjs概述-中文
10 阅读
2
ExpressAPI
10 阅读
3
NestJS新手入门核心模块对比表笔记
9 阅读
4
JavaScript企业数据处理实用指南
9 阅读
5
Vue2详细笔记
8 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
12
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
4
篇与
的结果
2026-02-24
NestJS新手入门核心模块对比表笔记
NestJS新手入门核心模块对比表笔记说明:表格严格遵循NestJS官方文档定义,重点标注各模块「声明函数/接口」「核心用法」,适配快速记忆,所有接口、装饰器均与官方保持一致,如内容有问题可以联系作者修改,谢谢。模块名称官方核心定位声明函数/核心接口(必填)接口参数/核心方法官方核心装饰器执行时机官方核心作用Controllers (控制器)请求处理入口,负责接收客户端请求并返回响应无强制接口,通过「类+装饰器」声明请求装饰器参数(路由路径、请求方法);处理方法接收请求参数@Controller()、@Get()、@Post()、@Param()、@Body()、@Query()请求进入后,中间件之后路由映射、请求接收、响应返回Providers (提供者)可注入的依赖单元,封装业务逻辑(核心为Service)无接口,通过@Injectable()标记类即可声明自定义业务方法(如create、findAll),支持构造函数注入@Injectable()(必填,标记为可注入)被依赖组件(如Controller)调用时封装业务逻辑、解耦、支持依赖注入Modules (模块)应用组织单元,管理模块内组件及依赖关系无接口,通过@Module()装饰器声明类@Module()配置对象(providers、controllers、imports、exports)@Module()(必填)、@Global()(可选,全局模块)应用启动时加载划分功能边界、管理依赖导入导出、组件共享Middleware (中间件)请求处理流程中的中间层,可拦截请求/响应类式:implements NestMiddleware;函数式:直接声明函数use(req: Request, res: Response, next: NextFunction)(类式/函数式通用)@Injectable()(仅类式中间件必填)请求进入最前端,控制器之前日志记录、请求验证、跨域处理等通用逻辑Exception Filters (异常过滤器)统一处理应用中未捕获的异常,格式化异常响应implements ExceptionFiltercatch(exception: any, host: ArgumentsHost)(必填方法)@Injectable()(必填)、@Catch()(必填,指定捕获的异常类型)发生未捕获异常时统一异常响应格式、自定义异常处理逻辑Pipes (管道)处理控制器方法参数,实现参数验证、转换implements PipeTransform<T, R>(T:输入类型,R:输出类型)transform(value: T, metadata: ArgumentMetadata)(必填方法)@Injectable()(必填)控制器方法调用前,参数解析后参数验证、参数类型转换、数据格式化Guards (守卫)请求授权控制,决定请求是否能进入控制器方法implements CanActivatecanActivate(context: ExecutionContext)(必填方法,返回布尔值)@Injectable()(必填)中间件之后,管道之前权限校验、角色控制、请求合法性判断Interceptors (拦截器)面向切面编程,拦截控制器方法执行前后逻辑implements NestInterceptor<T, R>(T:输入,R:输出)intercept(context: ExecutionContext, next: CallHandler)(必填方法)@Injectable()(必填)管道之后,控制器方法执行前后日志记录、响应映射、异常映射、请求/响应拦截Custom decorators (自定义装饰器)封装装饰器逻辑,扩展Nest装饰器功能参数装饰器:createParamDecorator;装饰器组合:applyDecoratorscreateParamDecorator((data, ctx) => { ... });applyDecorators(多个装饰器)无必填装饰器,可组合官方装饰器使用被装饰的类/方法/参数初始化时简化代码、复用装饰器逻辑、扩展官方功能
2026年02月24日
9 阅读
0 评论
0 点赞
2026-02-24
NestJS 内置 Pipes笔记
NestJS 内置 Pipes笔记一、概述Nest 内置 Pipe 均从 @nestjs/common 导出,用于参数验证、类型转换、默认值等场景,可直接在控制器路由、DTO、全局配置中使用。二、内置 Pipe 清单与作用1. 验证类1.1 ValidationPipe作用:最常用的全局验证管道,配合 class-validator + class-transformer 对 DTO 进行自动校验。场景:校验请求体、查询参数、路径参数的合法性。常用配置:whitelist: true:过滤掉非 DTO 定义的字段forbidNonWhitelisted: true:禁止传入多余字段transform: true:自动将原始值转为 DTO 对应类型2. 基础类型转换2.1 ParseIntPipe作用:将参数转为整数,转换失败抛出 400 Bad Request。场景:id、page、limit 等数字路径参数。2.2 ParseFloatPipe作用:转为浮点数。场景:价格、经纬度、小数类参数。2.3 ParseBoolPipe作用:转为布尔值,支持 true/false、1/0、'true'/'false'。场景:开关、状态查询参数。3. 格式/结构校验3.1 ParseUUIDPipe作用:校验参数是否为合法 UUID(v4 等),不合法则报错。场景:用户 ID、订单号等 UUID 格式参数。3.2 ParseEnumPipe作用:校验参数是否属于指定枚举。场景:状态、类型、固定选项参数。3.3 ParseArrayPipe作用:将字符串转为数组,可指定分隔符、校验每项类型。场景:批量 ID、多选参数 ?ids=1,2,3。3.4 ParseDatePipe作用:将字符串/时间戳转为 Date 对象,非法日期报错。场景:开始时间、结束时间等日期参数。4. 文件与默认值4.1 DefaultValuePipe作用:为参数提供默认值,不与其他 Pipe 冲突。场景:分页默认 page=1、limit=10。4.2 ParseFilePipe作用:上传文件校验,可校验文件类型、大小、数量等。场景:单文件/多文件上传校验。三、典型使用方式(极简示例)1. 路由参数直接使用@Get(':id') getOne(@Param('id', ParseIntPipe) id: number) {}2. 带默认值@Get() getList( @Query('page', ParseIntPipe, new DefaultValuePipe(1)) page: number, ) {}3. 枚举校验@Get('type/:type') getByType( @Param('type', new ParseEnumPipe(StatusEnum)) type: StatusEnum, ) {}4. 全局启用 ValidationPipe(推荐)// main.ts app.useGlobalPipes( new ValidationPipe({ transform: true, whitelist: true, }), );四、使用总结校验 + 转换:统一由 Pipe 完成,控制器代码更干净。失败自动抛错:返回标准 400 错误,无需手动判断。可组合:多个 Pipe 可按顺序链式使用。
2026年02月24日
3 阅读
0 评论
0 点赞
2026-02-24
Nest.js 内置 HTTP 异常笔记
Nest.js 内置 HTTP 异常笔记一、核心概念Nest.js 从 @nestjs/common 导出的所有内置 HTTP 异常均继承自 HttpException 基类,每个异常对应标准的 HTTP 状态码,用于在控制器/服务中快速抛出符合 RESTful 规范的错误响应,无需手动拼接状态码和响应格式。二、完整内置异常列表(含状态码+使用场景)异常类名HTTP 状态码核心使用场景BadRequestException400请求参数错误、格式非法(如参数校验失败)UnauthorizedException401未认证(如无 token、token 无效/过期)NotFoundException404资源不存在(如查询 ID 不存在的用户/文章)ForbiddenException403已认证但无权限操作(如普通用户访问管理员接口)NotAcceptableException406服务器无法生成客户端请求的内容格式(如客户端要求 XML 但仅支持 JSON)RequestTimeoutException408请求超时(如客户端请求未在指定时间内完成)ConflictException409资源冲突(如创建已存在的用户名、重复提交表单)GoneException410资源永久删除(如已下架的商品,无法恢复)HttpVersionNotSupportedException505不支持的 HTTP 协议版本(如客户端用 HTTP/3 但服务器仅支持 HTTP/1.1)PayloadTooLargeException413请求体过大(如上传文件超过服务器限制)UnsupportedMediaTypeException415不支持的媒体类型(如上传图片时 Content-Type 为 text/plain)UnprocessableEntityException422请求体格式正确但语义错误(如手机号格式正确但非合法号码)InternalServerErrorException500服务器内部错误(如代码逻辑异常、数据库连接失败)NotImplementedException501接口未实现(如规划中的接口暂未开发)ImATeapotException418趣味异常(RFC 2324 定义,实际极少使用,可用于测试)MethodNotAllowedException405请求方法不允许(如 GET 访问仅支持 POST 的接口)BadGatewayException502网关错误(如 Nest 作为网关转发请求时,后端服务返回无效响应)ServiceUnavailableException503服务不可用(如服务器维护、过载,暂时无法处理请求)GatewayTimeoutException504网关超时(如 Nest 转发请求时,后端服务响应超时)PreconditionFailedException412前置条件失败(如请求头中 If-Match 与服务器资源版本不匹配)三、基础使用方式1. 最简使用(仅指定错误消息)import { Controller, Get, NotFoundException } from '@nestjs/common'; @Controller('users') export class UsersController { @Get(':id') findOne(@Param('id') id: string) { const user = null; // 模拟查询不到用户 if (!user) { // 仅指定消息,error 字段会默认显示 HTTP 状态码对应的默认描述(如 "Not Found") throw new NotFoundException(`User with ID ${id} not found`); } return user; } }响应结果:{ "message": "User with ID 123 not found", "error": "Not Found", "statusCode": 404 }2. 完整使用(指定消息+原因+描述)import { Controller, Post, BadRequestException } from '@nestjs/common'; @Controller('users') export class UsersController { @Post() create(@Body() createUserDto: any) { try { // 模拟参数校验失败 if (!createUserDto.username) { throw new Error('Username is required'); } } catch (error) { throw new BadRequestException('Failed to create user', { cause: error, // 原始错误(用于日志排查,不会返回给客户端) description: 'Username is a mandatory field', // 客户端可见的详细描述 }); } } }响应结果:{ "message": "Failed to create user", "error": "Username is a mandatory field", "statusCode": 400 }四、进阶用法:自定义 HTTP 异常若内置异常无法满足需求(如自定义状态码/响应格式),可继承 HttpException 实现自定义异常:import { HttpException, HttpStatus } from '@nestjs/common'; // 自定义异常:请求频率过高(429 状态码) export class TooManyRequestsException extends HttpException { constructor(message: string = 'Too many requests', description?: string) { super( { message, error: description || 'Too Many Requests', statusCode: HttpStatus.TOO_MANY_REQUESTS, // 429 }, HttpStatus.TOO_MANY_REQUESTS, ); } } // 使用自定义异常 @Controller('api') export class ApiController { @Get() getData() { throw new TooManyRequestsException('请求过于频繁', '请等待 60 秒后重试'); } }响应结果:{ "message": "请求过于频繁", "error": "请等待 60 秒后重试", "statusCode": 429 }五、全局异常过滤器(统一响应格式)实际开发中,通常会自定义全局异常过滤器,统一所有 HTTP 异常的响应格式(如增加 timestamp 字段):import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Response } from 'express'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const status = exception.getStatus(); const exceptionResponse = exception.getResponse() as { message: string; error: string }; response.status(status).json({ code: status, // 自定义码段 msg: exceptionResponse.message, detail: exceptionResponse.error, timestamp: new Date().toISOString(), // 错误时间 path: ctx.getRequest().url, // 请求路径 }); } } // 在 main.ts 注册全局过滤器 import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { HttpExceptionFilter } from './filters/http-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new HttpExceptionFilter()); // 全局生效 await app.listen(3000); } bootstrap();统一后的响应示例:{ "code": 400, "msg": "Something bad happened", "detail": "Some error description", "timestamp": "2026-02-24T10:00:00.000Z", "path": "/users" }总结Nest 内置 HTTP 异常覆盖了所有常见 HTTP 状态码,每个异常对应明确的业务场景,可直接抛出无需手动处理状态码;内置异常支持自定义 message、cause(日志用)和 description(客户端可见),兼顾排查和用户体验;若内置异常不满足需求,可继承 HttpException 实现自定义异常,或通过全局过滤器统一响应格式。
2026年02月24日
7 阅读
0 评论
1 点赞
2026-02-16
Nestjs概述-中文
INTRODUCTION一、简介Nest(NestJS)是一款用于构建高效、可扩展的 Node.js 服务端应用框架。采用渐进式 JavaScript 设计,基于并完整支持 TypeScript(同时也支持纯 JavaScript 开发)。融合了三大编程范式:OOP:面向对象编程FP:函数式编程FRP:函数式响应式编程底层原理:默认使用成熟的 HTTP 服务框架 Express。也可灵活配置切换为 Fastify。框架特点:在 Express / Fastify 等通用 Node 框架之上做了一层上层抽象。同时直接暴露底层原生 API,开发者可以自由使用平台上大量的第三方模块。Node.js 服务端、TS 优先、三大编程范式、Express/Fastify 底层、抽象 + 原生 API 双支持二、设计理念近年来,得益于 Node.js,JavaScript 已经成为前后端通用的“网络通用语言”。这也催生了 Angular、React、Vue 等优秀前端框架,大幅提升开发效率,让前端应用更快速、可测试、易扩展。但在 Node.js(服务端 JavaScript)生态中:虽然已有大量优质库、工具和辅助模块但没有一个能有效解决架构问题NestJS 提供开箱即用的应用架构,让开发者和团队可以轻松构建:高可测试性高可扩展性低耦合易维护的应用程序。其架构设计深受 Angular 启发。三、安装方式 1:使用 Nest CLI 搭建(推荐新手)通过命令行脚手架直接生成项目目录、核心文件和标准目录结构。# 全局安装 CLI npm i -g @nestjs/cli # 创建项目 nest new project-name提示:如需创建更严格类型检查的 TypeScript 项目,可添加 --strict 参数:nest new project-name --strict方式 2:Git 克隆官方起步模板# 克隆 TS 模板 git clone https://github.com/nestjs/typescript-starter.git project cd project npm install npm run start提示:如果不想克隆 Git 历史,可以使用 degit。启动后访问:http://localhost:3000/如需纯 JavaScript 版本,将仓库地址换成 javascript-starter.git 即可。方式 3:从零手动安装(不推荐新手)只安装核心包,需自己配置项目文件,最少依赖:NestJS 四大核心依赖作用核心必装依赖(缺一不可,支撑 Nest 基础运行):@nestjs/core:Nest 核心引擎,提供应用初始化、模块挂载、依赖注入底层实现,是应用运行基石。@nestjs/common:通用工具库,提供控制器、服务等核心装饰器及异常处理、管道等基础组件,简化开发。rxjs:响应式编程库,处理异步数据流,支撑 Nest 异步操作(如请求、数据库查询)。reflect-metadata:元数据反射工具,支撑装饰器识别和依赖注入机制,保障组件自动装配。补充:CLI 搭建/克隆模板会自动安装,从零搭建需手动安装这四个依赖。OVERVIEWfirst steps一、入门基础入门目标通过构建基础CRUD应用,掌握Nest核心基础知识和关键组成部分,覆盖入门级核心知识点。语言支持兼容TypeScript(推荐)和纯JavaScript,纯JS需搭配Babel编译器(Nest依赖最新语言特性)。示例默认使用TypeScript,代码片段可点击右上角按钮,切换为纯JavaScript语法。前置条件必须安装Node.js,且版本≥20,否则无法正常搭建和运行Nest项目。二、项目搭建(推荐CLI方式)npm i -g @nestjs/cli # 全局安装Nest CLI工具 nest new project-name # 创建新的Nest项目(project-name为自定义项目名)提示:添加--strict标志,可创建启用TypeScript严格特性集的项目。项目核心目录与文件CLI搭建后会生成src目录,包含5个核心文件,分工明确:app.controller.ts:基础控制器,包含单一路由,负责接收和响应请求。app.controller.spec.ts:控制器的单元测试文件,用于测试控制器功能。app.module.ts:应用的根模块,是Nest应用的核心组织单元。app.service.ts:基础服务,包含单个方法,用于封装业务逻辑。main.ts:应用入口文件,通过NestFactory创建应用实例并启动。三、平台支持Nest是平台无关框架,开箱即支持两种HTTP平台:Express(默认)和Fastify。默认使用@nestjs/platform-express包,Express成熟稳定、社区资源丰富;Fastify高性能、低开销,可按需切换。无需指定平台类型,仅当需要访问底层平台API时,才需在创建应用实例时指定类型。四、应用运行命令npm run start # 普通启动,监听HTTP请求 npm run start:dev # 热更新启动,监听文件变化,自动重新编译并重启服务器 npm run start -- -b swc # 加速构建(构建速度提升20倍)补充:应用启动后,打开浏览器访问http://localhost:3000/,可看到Hello World! 提示,说明启动成功。五、代码检查与格式化CLI生成的项目默认预装eslint(代码检查工具)和prettier(代码格式化工具),支持IDE集成和无头环境使用。npm run lint # 检查代码规范,并自动修复可修复的问题 npm run format # 格式化代码,统一代码风格Controllers一、控制器核心定位Controllers(控制器)负责处理客户端传入的请求,并向客户端返回响应,是Nest应用中处理请求的核心组件。核心作用:接收特定请求、执行对应逻辑、返回响应,路由机制决定了哪个控制器处理哪个请求。一个控制器可包含多个路由,每个路由对应不同的请求处理逻辑(动作)。创建方式:通过类和装饰器定义,装饰器为类添加元数据,让Nest生成请求与控制器的路由映射。提示:使用CLI命令快速生成CRUD控制器(带内置验证):nest g resource [name];快速生成普通控制器:nest g controller [name]。二、核心基础:路由(Routing)路由装饰器核心用法@Controller():定义基础控制器,可指定路由路径前缀(如@Controller('cats')),用于分组相关路由、减少重复代码。HTTP请求方法装饰器:@Get()、@Post()、@Put()、@Delete()等,修饰控制器方法,指定该方法处理的HTTP请求类型。路由路径拼接:控制器前缀 + 方法装饰器中的路径(可选),例如:@Controller('cats') + @Get('breed') → 路由为GET /cats/breed。基础示例(cats.controller.ts)import { Controller, Get } from '@nestjs/common'; @Controller('cats') // 路由前缀:/cats export class CatsController { @Get() // 处理GET /cats请求 findAll(): string { return 'This action returns all cats'; } }路由通配符支持模式匹配路由,常用作为通配符(匹配路径末尾任意字符),例如:@Get('abcd/') 可匹配 /abcd/123、/abcd/abc 等路径。注意:Express v5及以上对路由要求更严格,Nest提供兼容层,可正常使用通配符;中间带的路由(如ab*cd)仅Express支持(需命名通配符),Fastify不支持。三、请求处理核心细节请求对象(Request Object)通过@Req()(或@Request())装饰器注入底层平台(默认Express)的请求对象,需导入express的Request类型(需安装@types/express)。无需手动获取请求参数,Nest提供专用装饰器,简化开发(常用装饰器如下):@Req() / @Request()获取完整请求对象(req)@Param(key?)获取路由参数(req.params),可指定具体key(如@Param('id'))@Body(key?)获取请求体(req.body),可指定具体key,常用于POST/PUT请求@Query(key?)获取查询参数(req.query),可指定具体key@Headers(name?)获取请求头(req.headers),可指定具体请求头名称路由参数(Route Parameters)用于接收URL中的动态数据(如GET /cats/1,获取id为1的猫),通过@Param()装饰器访问。// 路由:GET /cats/:id @Get(':id') findOne(@Param('id') id: string): string { return `This action returns a #${id} cat`; }注意:带参数的路由需声明在静态路由之后,避免拦截静态路由请求。请求体(Request Payloads)通过@Body()装饰器获取请求体,TypeScript环境下需定义DTO(数据传输对象),指定请求体格式。DTO推荐用类(而非接口),因为接口会在编译时被移除,Nest无法在运行时引用(如管道验证需依赖DTO元数据)。// create-cat.dto.ts(DTO定义) export class CreateCatDto { name: string; age: number; breed: string; } // cats.controller.ts(使用DTO) import { Controller, Post, Body } from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; @Controller('cats') export class CatsController { @Post() // 处理POST /cats请求 create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; } }查询参数(Query Parameters)通过@Query()装饰器获取URL中的查询参数(如GET /cats?age=2&breed=Persian),支持复杂查询参数(需配置HTTP适配器的查询解析器)。四、响应处理(Response Handling)两种响应方式标准方式(推荐):直接返回值,Nest自动处理序列化和状态码。返回对象/数组:自动序列化为JSON;返回基础类型(字符串、数字):直接返回值。默认状态码:GET/PUT/DELETE等为200,POST为201,可通过@HttpCode()装饰器修改。库特定方式:通过@Res()(或@Response())注入底层响应对象(如Express的res),手动控制响应。注意:使用@Res()后,Nest会禁用标准方式;需手动调用res.send()/res.json()等,否则请求会挂起。兼容两种方式:添加passthrough: true(@Res({ passthrough: true })),可手动设置响应头/ cookies,同时让Nest处理返回值。状态码与响应头设置状态码:@HttpCode(状态码) 装饰器(需从@nestjs/common导入),动态状态码可通过响应对象或抛出异常设置。响应头:@Header(键, 值) 装饰器,或通过响应对象手动设置(res.header())。import { Controller, Post, HttpCode, Header } from '@nestjs/common'; @Controller('cats') export class CatsController { @Post() @HttpCode(204) // 设置状态码为204 @Header('Cache-Control', 'no-store') // 设置响应头 create() { return 'This action adds a new cat'; } }重定向(Redirection)通过@Redirect(url, 状态码) 装饰器实现重定向,状态码默认302;动态重定向可返回一个包含url和statusCode的对象,覆盖装饰器参数。五、控制器进阶特性1. 子域名路由(Sub-domain Routing)@Controller()装饰器可传入host选项,指定请求的主机名(如@Controller({ host: 'admin.example.com' })),支持动态主机名(用:占位符),通过@HostParam()获取主机名参数。注意:Fastify不支持嵌套路由器,使用子域名路由推荐用默认Express适配器。2. 异步处理(Asynchronicity)完全支持async/await,异步方法返回Promise,Nest自动解析。支持返回RxJS Observable流,Nest自动订阅,流完成后返回最终值。3. 状态共享(State Sharing)Nest中几乎所有资源(数据库连接池、单例服务等)都在请求间共享,Node.js不使用“请求/响应多线程无状态模型”,因此使用单例实例是安全的;特殊场景需请求级生命周期,可配置注入作用域。???六、控制器注册与完整示例控制器注册控制器必须属于某个模块,需在@Module()装饰器的controllers数组中注册(通常注册在根模块AppModule或对应功能模块)。// app.module.ts import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; @Module({ controllers: [CatsController], // 注册CatsController }) export class AppModule {}完整CRUD控制器示例import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common'; import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto'; @Controller('cats') export class CatsController { @Post() // 新增 create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; } @Get() // 查询所有 findAll(@Query() query: ListAllEntities) { return `This action returns all cats (limit: ${query.limit} items)`; } @Get(':id') // 查询单个 findOne(@Param('id') id: string) { return `This action returns a #${id} cat`; } @Put(':id') // 更新 update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) { return `This action updates a #${id} cat`; } @Delete(':id') // 删除 remove(@Param('id') id: string) { return `This action removes a #${id} cat`; } }七、关键提示DTO用于规范请求数据格式,配合ValidationPipe可实现请求数据校验和过滤(过滤非白名单属性)。库特定响应方式(@Res())会使代码平台依赖,增加测试难度,非必要不推荐使用。CLI生成器可自动创建控制器及相关模板,提升开发效率。Provides一、Providers 核心定位Providers(提供者)是 Nest 的核心概念,许多基础 Nest 类(如服务、仓库、工厂、工具类等)都可作为提供者。核心思想:可作为依赖被注入,使对象之间形成各种关联关系,对象间的“连接”工作主要由 Nest 运行时系统处理。核心作用:封装业务逻辑、数据处理(如数据存储与查询)等复杂任务,供控制器(Controller)调用,实现“控制器负责请求响应、提供者负责业务逻辑”的分离。本质:普通 JavaScript/TypeScript 类,需在 Nest 模块中声明为提供者才能被 Nest 管理和注入。提示:推荐遵循 SOLID 原则设计和组织依赖,让代码更具可维护性和扩展性。**SOLID 原则是 5 条面向对象设计(OOP)的核心准则 S(单一职责原则) 核心:一个 Provider(如 Service)只负责一项核心职责,不承担无关逻辑。 O(开放 / 封闭原则) 核心:Provider 对扩展开放、对修改封闭。新增功能时,通过扩展类 / 方法实现,不改动原有代码。 L(里氏替换原则) 核心:子类可完全替代父类,且不影响程序正常运行(父类与子类遵循同一接口 / 规范)。 I(接口隔离原则) 核心:不强迫 Provider 依赖它用不到的接口 / 方法,拆分细化接口,避免 “胖接口”。 D(依赖倒置原则) 核心:依赖抽象(接口 / 抽象类),不依赖具体实现;高层模块(如 Controller)不依赖低层模块(如 Service),二者都依赖抽象。**二、核心实现:Services(服务)Services 是最常用的 Providers 类型,专门用于封装业务逻辑,是控制器的“业务处理助手”。服务的创建与基础示例通过 @Injectable() 装饰器标记类为服务(提供者),CLI 命令快速生成服务:nest g service [name](如 nest g service cats)。// cats.service.ts(服务定义) import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; // 导入接口规范数据格式 @Injectable() // 标记此类为Nest可管理的提供者 export class CatsService { private readonly cats: Cat[] = []; // 模拟数据存储 // 新增猫的逻辑 create(cat: Cat) { this.cats.push(cat); } // 查询所有猫的逻辑 findAll(): Cat[] { return this.cats; } }2. 配套接口(Interface)通常会用接口(Interface)规范数据格式(如 Cat 接口),仅用于 TypeScript 类型校验,编译后会被移除。// interfaces/cat.interface.ts export interface Cat { name: string; age: number; breed: string; }3. 服务的使用(注入到控制器)通过构造函数注入服务,控制器即可调用服务中的方法,实现业务逻辑分离。// cats.controller.ts(使用CatsService) import { Controller, Get, Post, Body } from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; import { CatsService } from './cats.service'; import { Cat } from './interfaces/cat.interface'; @Controller('cats') export class CatsController { // 构造函数注入CatsService,private关键字简化声明+初始化 constructor(private catsService: CatsService) {} @Post() async create(@Body() createCatDto: CreateCatDto) { // 调用服务的create方法处理新增逻辑 this.catsService.create(createCatDto); } @Get() async findAll(): Promise<Cat[]> { // 调用服务的findAll方法获取数据 return this.catsService.findAll(); } }三、核心机制:依赖注入(Dependency Injection)依赖注入(DI)是 Nest 的核心设计模式,Nest 基于 TypeScript 的特性,让依赖管理变得简单。核心原理:Nest 会根据构造函数中声明的类型,自动解析并注入对应的依赖实例(如构造函数中声明 private catsService: CatsService,Nest 会自动创建/获取 CatsService 实例并注入)。单例特性:默认情况下,提供者是单例的(整个应用生命周期内只有一个实例),多次注入会获取同一个实例。优势:降低代码耦合度,便于测试和维护,无需手动创建和管理依赖实例。四、Providers 核心特性1. 作用域(Scopes)提供者的生命周期(作用域)默认与应用生命周期一致:应用启动时:所有依赖被解析,提供者实例化。应用关闭时:所有提供者被销毁。可选作用域:可配置为“请求作用域”(request-scoped),即每个请求对应一个提供者实例,生命周期与单个请求绑定(详见注入作用域章节)。2. 自定义提供者(Custom Providers)Nest 内置控制反转(IoC)容器,支持多种方式定义提供者,不止于类:支持类型:普通值、类、异步工厂、同步工厂等。核心用途:灵活配置依赖,如注入第三方库、模拟测试依赖等(详见依赖注入章节)。3. 可选提供者(Optional Providers)用于处理“非必需依赖”(如可选的配置对象),即使依赖未被提供,也不会报错,可使用默认值。通过 @Optional() 装饰器标记为可选依赖,通常配合自定义提供者的令牌使用。import { Injectable, Optional, Inject } from '@nestjs/common'; @Injectable() export class HttpService<T> { // @Optional() 标记为可选依赖,@Inject() 指定自定义令牌 constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {} }4. 属性注入(Property-based Injection)除了常用的“构造函数注入”,还可通过@Inject() 装饰器直接注入到类属性上。适用场景:子类继承父类时,避免通过 super() 层层传递依赖(非必要不推荐,构造函数注入更清晰)。import { Injectable, Inject } from '@nestjs/common'; @Injectable() export class HttpService<T> { // 直接注入到属性上 @Inject('HTTP_OPTIONS') private readonly httpClient: T; }五、提供者注册(Provider Registration)提供者必须在某个 Nest 模块中注册,才能被 Nest 识别、管理和注入(与控制器注册类似)。注册方式:在模块的 @Module() 装饰器中,将提供者添加到 providers 数组中。// app.module.ts(注册CatsService) import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; import { CatsService } from './cats/cats.service'; @Module({ controllers: [CatsController], // 注册控制器 providers: [CatsService], // 注册提供者(服务) }) export class AppModule {}注册后,Nest 即可正常解析控制器(如 CatsController)对该提供者的依赖。六、目录结构参考结合此前的控制器、DTO、接口,整合提供者后的标准目录结构如下:src ├─ cats │ ├─ dto │ │ └─ create-cat.dto.ts │ ├─ interfaces │ │ └─ cat.interface.ts │ ├─ cats.controller.ts │ └─ cats.service.ts ├─ app.module.ts └─ main.ts七、手动实例化(补充)默认情况下,Nest 自动处理依赖的解析和实例化,特殊场景下可手动获取/实例化提供者:Module reference:动态获取已存在的提供者实例或动态实例化提供者。Standalone applications:在 bootstrap() 函数中获取提供者(如独立应用、启动时使用配置服务)。八、关键提示@Injectable() 装饰器是核心:必须给提供者类添加该装饰器,否则 Nest 无法识别和管理。分离原则:控制器只处理 HTTP 请求/响应,复杂业务逻辑全部交给提供者(服务),提升代码可维护性。动态获取已存在的提供者实例或动态实例化提供者 (默认情况下,Nest 会在应用启动时(bootstrap 阶段),自动解析所有依赖、实例化提供者(单例),并在需要时(如控制器注入)自动注入。而 “动态获取 / 动态实例化”,是打破这种 “启动时自动处理” 的固定模式,在应用运行过程中(比如请求处理中、某个逻辑执行时),手动获取已经存在的提供者实例,或手动创建新的提供者实例(按需实例化,而非启动时就创建)。)Modules一、Modules 核心定位Modules是被@Module()装饰器标记的类,核心作用是作为应用“组织单元”,封装相关控制器、提供者,管理依赖、划分功能边界,让应用结构清晰可维护。核心特性:每个Nest应用至少有一个根模块(AppModule),是Nest构建应用图(解析依赖关系)的起点。使用场景:小型应用可仅用根模块;中大型应用拆分多个模块,每个模块封装一组紧密相关功能(贴合SOLID原则)。提示:CLI快速生成模块:nest g module [name](如nest g module cats)。二、@Module() 装饰器核心配置@Module()接收一个对象,包含4个核心属性(按需配置,无需全部填写),用于描述模块配置:providers模块内的提供者(如Service),由Nest实例化,可在当前模块内共享。controllers模块内的控制器,Nest自动实例化并注册路由。imports需导入的模块列表,导入模块需导出当前模块所需提供者(导入才可使用)。exports模块对外暴露的提供者(模块“公共API”),其他模块导入后可使用,可导出提供者本身或其令牌。关键规则:模块默认封装提供者,仅可注入“当前模块内提供者”或“其他导入模块明确导出的提供者”。三、常见模块类型及用法1. 功能模块(Feature Modules)最常用,封装某一特定功能的控制器和提供者,划分功能边界(如Cats相关功能封装为CatsModule)。示例:创建并使用CatsModule// cats/cats.module.ts(功能模块定义) import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule {}需导入根模块才可使用:// app.module.ts(根模块导入) import { Module } from '@nestjs/common'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule] }) export class AppModule {}2. 共享模块(Shared Modules)Nest模块默认是单例,可在多模块间共享同一提供者实例,只需导出该提供者即可。核心用法:在exports数组中添加需共享的提供者,其他模块导入该模块即可使用(共享同一实例)。优势:避免重复注册,减少内存占用,保证状态一致性。// cats/cats.module.ts(共享CatsService) import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], exports: [CatsService] // 导出供其他模块共享 }) export class CatsModule {}3. 模块重导出(Module Re-exporting)模块可重导出其导入的模块,其他模块导入当前模块后,即可使用被重导出模块的功能,无需单独导入。// core.module.ts(重导出CommonModule) import { Module } from '@nestjs/common'; import { CommonModule } from './common.module'; @Module({ imports: [CommonModule], exports: [CommonModule] // 重导出 }) export class CoreModule {}4. 全局模块(Global Modules)用于提供全局可用的提供者(如工具类、数据库连接),无需重复导入,用@Global()标记。核心用法:添加@Global(),同时导出需全局共享的提供者;只需注册一次(通常由根模块注册)。注意:不推荐过度使用,会增加模块耦合,优先用“导入+导出”共享功能。// cats/cats.module.ts(全局模块) import { Module, Global } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Global() @Module({ controllers: [CatsController], providers: [CatsService], exports: [CatsService] // 必须导出才可全局使用 }) export class CatsModule {}5. 动态模块(Dynamic Modules)可在运行时配置的模块,适用于灵活定制场景(如根据配置创建数据库连接)。核心用法:通过静态方法(通常forRoot()/forRootAsync())返回DynamicModule,动态配置providers、exports。特性:静态方法可同步/异步返回;动态配置扩展(不覆盖)模块默认配置。// database/database.module.ts(动态模块) import { Module, DynamicModule } from '@nestjs/common'; import { createDatabaseProviders } from './database.providers'; import { Connection } from './connection.provider'; @Module({ providers: [Connection], exports: [Connection] }) export class DatabaseModule { static forRoot(entities = [], options?): DynamicModule { const providers = createDatabaseProviders(options, entities); // 动态生成提供者 return { module: DatabaseModule, providers, exports: providers }; } }使用示例(导入时传配置):// app.module.ts import { Module } from '@nestjs/common'; import { DatabaseModule } from './database/database.module'; import { User } from './users/entities/user.entity'; @Module({ imports: [DatabaseModule.forRoot([User])] }) export class AppModule {}四、模块相关补充1. 模块中的依赖注入 模块类可注入提供者(用于自身配置),但不能作为提供者被注入(会导致循环依赖)。模块(如 UserModule)管理着服务(UserService),若服务再注入模块,会形成「模块 → 服务 → 模块」的循环 模块是“管理者”(管理控制器、提供者),可以用提供者来辅助自己工作,但模块不能当“被管理者”(不能被当作提供者,供其他组件注入使用),否则会出现依赖循环,Nest无法解析。// cats/cats.module.ts(模块注入提供者) import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService] }) export class CatsModule { constructor(private catsService: CatsService) {} // 模块注入 } 标准目录结构src ├─ cats // 功能模块目录 │ ├─ dto // 数据传输对象 │ ├─ interfaces // 接口定义 │ ├─ cats.controller.ts │ ├─ cats.module.ts // 模块文件 │ └─ cats.service.ts // 提供者文件 ├─ app.module.ts // 根模块 └─ main.ts // 入口文件五、关键提示模块核心价值:划分功能边界、管理依赖、实现复用,是中大型Nest应用的核心组织方式。exports注意:仅导出其他模块需用的提供者,减少模块耦合。全局模块vs共享模块:优先用共享模块(结构清晰),避免过度使用全局模块。动态模块核心:运行时配置,适配多场景(如多数据库、环境差异化配置)。Middleware一、核心概念:NestJS 中间件是什么?NestJS 中间件本质上和 Express 中间件等价,是在路由处理器执行前被调用的函数/类,它能访问 request(请求)、response(响应)对象,以及请求-响应周期中的 next() 函数,主要作用包括:执行任意代码(如日志打印、权限校验);修改请求/响应对象(如添加请求头、解析参数);结束请求-响应周期(如直接返回错误响应);调用下一个中间件(必须调用 next(),否则请求会“挂起”)。二、NestJS 中间件的两种实现方式1. 类式中间件(推荐:需要依赖注入时) nestmiddleware+injectable适用于中间件需要注入服务/依赖的场景,必须实现 NestMiddleware 接口,并使用 @Injectable() 装饰器。// src/common/middleware/logger.middleware.ts import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { // 核心方法:use 接收 req、res、next 三个参数 use(req: Request, res: Response, next: NextFunction) { console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl}`); // 必须调用 next(),否则请求会卡住 next(); } }2. 函数式中间件(推荐:无依赖时)更简洁,无需类和装饰器,适合简单场景(如基础日志、跨域)。// src/common/middleware/logger.middleware.ts import { Request, Response, NextFunction } from 'express'; // 直接导出一个函数即可 export function logger(req: Request, res: Response, next: NextFunction) { console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl}`); next(); }三、中间件的应用方式1. 局部应用(指定路由/控制器)通过模块实现 NestModule 接口,在 configure() 方法中使用 MiddlewareConsumer 绑定中间件,支持:指定单个/多个路由;指定请求方法(GET/POST 等);指定控制器;排除特定路由。示例1:绑定到指定路由(GET /cats)// src/app.module.ts import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsController } from './cats/cats.controller'; @Module({ controllers: [CatsController], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) // 也可以用函数式:apply(logger) .exclude( // 排除不需要应用的路由 { path: 'cats/ignore', method: RequestMethod.GET }, // 排除 GET /cats/ignore 'cats/*/delete' // 通配符排除 ) .forRoutes( { path: 'cats', method: RequestMethod.GET }, // 仅绑定 GET /cats CatsController // 也可以直接绑定整个控制器(所有 /cats 下的路由) ); } }2. 全局应用(所有路由)通过 app.use() 绑定到所有路由,注意:全局中间件无法使用依赖注入(类式中间件不行),优先用函数式。// src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { logger } from './common/middleware/logger.middleware'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 全局应用中间件(所有路由都会触发) app.use(logger); await app.listen(3000); } bootstrap();3. 多个中间件顺序执行apply() 方法可传入多个中间件,按传入顺序执行(如先跨域 → 再安全校验 → 再日志)。// src/app.module.ts import * as cors from 'cors'; import * as helmet from 'helmet'; // 安全相关中间件 import { logger } from './common/middleware/logger.middleware'; // 在 configure 中 consumer .apply(cors(), helmet(), logger) // 顺序:cors → helmet → logger .forRoutes(CatsController);四、关键注意事项通配符路由:'abcd/*':匹配 abcd/1、abcd/abc,但不匹配 abcd/;'abcd/{*splat}':加花括号后,可匹配 abcd/、abcd/123 等所有以 abcd/ 开头的路由;Express/Fastify 差异:两者中间件的方法签名不同,需根据适配器调整;全局中间件依赖注入:如果全局中间件需要依赖,不要用 app.use(),而是在模块中通过 forRoutes('*') 绑定类式中间件:// 模块中绑定全局类式中间件(支持依赖注入)默认中间件:使用 Express 适配器时,NestJS 会默认注册 json() 和 urlencoded() 中间件,如需自定义,创建应用时关闭:总结NestJS 中间件分类式(支持依赖注入)和函数式(简洁无依赖),核心是实现 use() 方法/函数并调用 next();中间件可局部绑定(指定路由/控制器,通过 MiddlewareConsumer)或全局绑定(所有路由,通过 app.use()); configure(consumer: MiddlewareConsumer) configure(consumer:middlewareconsumer)关键技巧:用 exclude() 排除特定路由、用通配符匹配批量路由、多个中间件按 apply() 传入顺序执行。通过这些方式,你可以灵活实现日志、权限校验、参数解析、跨域处理等通用功能,让业务代码更聚焦核心逻辑。Exception filters一、Exception filters 核心概念Nest 内置异常层(exceptions layer),负责处理应用中所有未被手动处理的异常;当应用代码未捕获异常时,该层会自动捕获并返回用户友好的响应。默认由内置全局异常过滤器处理,核心负责 HttpException 及其子类异常;对于未识别的异常(非 HttpException 及其子类),默认返回如下 JSON 响应:{ "statusCode": 500, "message": "Internal server error" }提示:全局异常过滤器部分支持 http-errors 库,任何包含 statusCode 和 message 属性的抛出异常,都会被正确格式化返回(而非默认的 500 异常)。二、抛出标准异常(Throwing standard exceptions)Nest 从 @nestjs/common 暴露内置 HttpException 类,适用于 HTTP REST/GraphQL API,推荐在错误场景下返回标准 HTTP 响应对象。2.1 基础示例// cats.controller.ts import { Get, HttpException, HttpStatus } from '@nestjs/common'; @Get() async findAll() { throw new HttpException('Forbidden', HttpStatus.FORBIDDEN); }客户端调用该接口时,响应如下:{ "statusCode": 403, "message": "Forbidden" }2.2 HttpException 构造函数参数response(必选):定义 JSON 响应体,可传入字符串(仅覆盖 message)或对象(覆盖整个响应体);status(必选):定义 HTTP 状态码,推荐使用 HttpStatus 枚举(来自 @nestjs/common);options(可选):传入错误原因(cause),不序列化到响应体,仅用于日志记录。2.3 自定义响应体 + 错误原因示例// cats.controller.ts @Get() async findAll() { try { await this.service.findAll() } catch (error) { throw new HttpException({ status: HttpStatus.FORBIDDEN, //httpstatus/forbidden error: 'This is a custom message', }, HttpStatus.FORBIDDEN, { cause: error // 错误原因,用于日志 }); } }对应响应:{ "status": 403, "error": "This is a custom message" }三、异常日志(Exceptions logging)默认情况下,异常过滤器不记录 HttpException 及其子类、WsException、RpcException 等内置异常(视为正常应用流程);这些异常均继承自 IntrinsicException(来自 @nestjs/common),用于区分“正常应用异常”和“非预期异常”;若需记录这些异常,需自定义异常过滤器。四、自定义异常(Custom exceptions)多数场景可直接使用内置异常,若需定制,推荐继承 HttpException 构建异常层级,Nest 会自动识别并处理响应。示例:自定义 ForbiddenException// forbidden.exception.ts import { HttpException, HttpStatus } from '@nestjs/common'; export class ForbiddenException extends HttpException { constructor() { super('Forbidden', HttpStatus.FORBIDDEN); } }使用方式(与内置异常一致):// cats.controller.ts @Get() async findAll() { throw new ForbiddenException(); }五、内置 HTTP 异常(Built-in HTTP exceptions)Nest 提供一系列继承自 HttpException 的标准异常(均来自 @nestjs/common),覆盖常见 HTTP 错误场景:BadRequestException、UnauthorizedException、NotFoundException、ForbiddenExceptionNotAcceptableException、RequestTimeoutException、ConflictException、GoneExceptionHttpVersionNotSupportedException、PayloadTooLargeException、UnsupportedMediaTypeExceptionUnprocessableEntityException、InternalServerErrorException、NotImplementedExceptionImATeapotException、MethodNotAllowedException、BadGatewayExceptionServiceUnavailableException、GatewayTimeoutException、PreconditionFailedException内置异常使用示例(带原因和描述)throw new BadRequestException('Something bad happened', { cause: new Error(), // 错误原因(日志用) description: 'Some error description' // 错误描述(响应中显示) });对应响应:{ "message": "Something bad happened", "error": "Some error description", "statusCode": 400 }六、异常过滤器(Exception filters)内置过滤器可处理多数场景,若需完全控制异常层(如添加日志、自定义响应格式),可自定义异常过滤器,控制响应流程和内容。6.1 自定义 HttpExceptionFilter(捕获 HttpException)// http-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch(HttpException) // 绑定要捕获的异常类型 export class HttpExceptionFilter implements ExceptionFilter { // 必须实现 catch 方法 catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); // 切换到 HTTP 上下文 const response = ctx.getResponse<Response>(); // 获取响应对象 const request = ctx.getRequest<Request>(); // 获取请求对象 const status = exception.getStatus(); // 获取异常状态码 // 自定义响应 response .status(status) .json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, }); } }提示:所有异常过滤器需实现 ExceptionFilter<T> 接口,T 为要捕获的异常类型,必须实现 catch(exception: T, host: ArgumentsHost) 方法。警告:若使用 @nestjs/platform-fastify,需用 response.send() 替代 response.json(),并导入 fastify 相关类型。6.2 @Catch() 装饰器用于绑定过滤器要捕获的异常类型,可传入单个类型或多个逗号分隔的类型,仅处理指定类型的异常。6.3 Arguments Host强大的工具对象,适用于所有执行上下文(HTTP、微服务、WebSocket),用于获取当前上下文的请求、响应等对象。示例中通过 host.switchToHttp() 切换到 HTTP 上下文,再通过 getResponse()、getRequest() 获取对应对象。七、绑定过滤器(Binding filters)通过 @UseFilters() 装饰器绑定(来自 @nestjs/common),支持方法级、控制器级、全局级三种作用域,推荐传入类(而非实例),支持依赖注入并减少内存占用。7.1 方法级作用域(Method-scoped)// cats.controller.ts import { Post, UseFilters, Body } from '@nestjs/common'; import { HttpExceptionFilter } from './http-exception.filter'; import { CreateCatDto } from './dto/create-cat.dto'; @Post() @UseFilters(HttpExceptionFilter) // 传入类,推荐方式 async create(@Body() createCatDto: CreateCatDto) { throw new ForbiddenException(); }也可传入实例(不推荐):@UseFilters(new HttpExceptionFilter())7.2 控制器级作用域(Controller-scoped)作用于控制器内所有路由处理器:// cats.controller.ts import { Controller, UseFilters } from '@nestjs/common'; import { HttpExceptionFilter } from './http-exception.filter'; @Controller() @UseFilters(HttpExceptionFilter) export class CatsController {}7.3 全局级作用域(Global-scoped)作用于整个应用的所有控制器和路由处理器,有两种实现方式。方式1:main.ts 中使用 useGlobalFilters()// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { HttpExceptionFilter } from './http-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new HttpExceptionFilter()); // 传入实例 await app.listen(process.env.PORT ?? 3000); } bootstrap();警告:useGlobalFilters() 不支持网关(gateways)或混合应用;且该方式注册的全局过滤器,无法注入依赖(脱离模块上下文)。方式2:模块中使用 APP_FILTER 令牌(支持依赖注入)// app.module.ts import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { HttpExceptionFilter } from './http-exception.filter'; @Module({ providers: [ { provide: APP_FILTER, useClass: HttpExceptionFilter, // 传入过滤器类 }, ], }) export class AppModule {}提示:该方式注册的过滤器仍为全局,推荐在过滤器所在的模块中配置;可注册多个过滤器,依次添加到 providers 数组即可。八、捕获所有异常(Catch everything)若需捕获所有未处理异常(无论类型),只需让 @Catch() 装饰器为空(不传入任何参数)。以下为平台无关的实现(使用 HTTP 适配器,不直接依赖 Express/Fastify 原生对象):import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; @Catch() // 空装饰器,捕获所有异常 export class CatchEverythingFilter implements ExceptionFilter { constructor(private readonly httpAdapterHost: HttpAdapterHost) {} catch(exception: unknown, host: ArgumentsHost): void { const { httpAdapter } = this.httpAdapterHost; const ctx = host.switchToHttp(); // 确定响应状态码 const httpStatus = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; // 自定义响应体 const responseBody = { statusCode: httpStatus, timestamp: new Date().toISOString(), path: httpAdapter.getRequestUrl(ctx.getRequest()), }; // 发送响应 httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus); } }警告:若同时使用“捕获所有异常”的过滤器和“捕获特定异常”的过滤器,需先声明“捕获所有”的过滤器,确保特定过滤器能正常生效。九、继承内置过滤器(Inheritance)若需扩展内置全局异常过滤器的行为,可继承 BaseExceptionFilter,并调用 super.catch() 委托异常处理给父类。9.1 自定义继承过滤器// all-exceptions.filter.ts import { Catch, ArgumentsHost } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; @Catch() export class AllExceptionsFilter extends BaseExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { // 可添加自定义逻辑(如日志) super.catch(exception, host); // 委托给父类处理 } }警告:方法级、控制器级的继承过滤器,不可用 new 实例化,需让框架自动实例化。9.2 全局继承过滤器的注册方式方式1:main.ts 中注入 HttpAdapter// main.ts async function bootstrap() { const app = await NestFactory.create(AppModule); const { httpAdapter } = app.get(HttpAdapterHost); // 传入 httpAdapter 实例化过滤器 app.useGlobalFilters(new AllExceptionsFilter(httpAdapter)); await app.listen(process.env.PORT ?? 3000); } bootstrap();方式2:模块中使用 APP_FILTER 令牌(推荐,支持依赖注入)// app.module.ts @Module({ providers: [ { provide: APP_FILTER, useClass: AllExceptionsFilter, }, ], }) export class AppModule {}Pipes一、Pipes 核心概念Pipe 是一个使用 @Injectable() 装饰器注解、并实现 PipeTransform 接口的类,主要作用于控制器路由处理器的参数,在方法被调用前介入,对参数进行处理后,再将(可能经过转换的)参数传递给路由处理器。1.1 Pipes 的两大典型用途转换(transformation):将输入数据转换为期望的格式(例如,将字符串转换为整数);验证(validation):校验输入数据,若合法则直接原样传递;若不合法则抛出异常。提示:Pipes 运行在异常区域内。这意味着当 Pipe 抛出异常时,会由异常层(全局异常过滤器和当前上下文应用的任何异常过滤器)处理;一旦 Pipe 抛出异常,后续的控制器方法将不会执行。这是在系统边界校验外部输入数据的最佳实践。二、内置 Pipes(Built-in pipes)Nest 提供了多个可直接开箱即用的内置 Pipes,均从 @nestjs/common 包导出,覆盖常见的转换和验证场景:ValidationPipe、ParseIntPipe、ParseFloatPipe、ParseBoolPipeParseArrayPipe、ParseUUIDPipe、ParseEnumPipe、DefaultValuePipeParseFilePipe、ParseDatePipe2.1 内置 Pipe 示例:ParseIntPipeParseIntPipe 是转换类 Pipes 的典型示例,确保方法处理器的参数被转换为 JavaScript 整数;若转换失败则抛出异常。以下示例同样适用于其他 Parse* 系列 Pipes(ParseBoolPipe、ParseFloatPipe 等)。三、绑定 Pipes(Binding pipes)使用 Pipe 需将其实例绑定到合适的上下文,常用参数级绑定(针对特定路由方法的参数),也支持方法级、控制器级、全局级绑定。绑定方式分为“传入类”(推荐,支持依赖注入)和“传入实例”(可自定义配置)。3.1 参数级绑定(最常用)示例1:传入类(默认配置)@Get(':id') async findOne(@Param('id', ParseIntPipe) id: number) { return this.catsService.findOne(id); }效果:若请求为 GET localhost:3000/abc(非数字 id),Nest 会抛出如下异常,阻止 findOne() 方法执行:{ "statusCode": 400, "message": "Validation failed (numeric string is expected)", "error": "Bad Request" }示例2:传入实例(自定义配置)通过实例化 Pipe 并传入配置,自定义异常状态码等行为:@Get(':id') async findOne( @Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE })) id: number, ) { return this.catsService.findOne(id); }3.2 其他参数类型的绑定示例1:绑定到查询参数(@Query)@Get() async findOne(@Query('id', ParseIntPipe) id: number) { return this.catsService.findOne(id); }示例2:ParseUUIDPipe 绑定(校验 UUID)@Get(':uuid') async findOne(@Param('uuid', new ParseUUIDPipe()) uuid: string) { return this.catsService.findOne(uuid); }提示:ParseUUIDPipe 默认支持 UUID 3、4、5 版本,若需指定特定版本,可在 Pipe 配置中传入 version 参数。提示:验证类 Pipes(如 ValidationPipe)的绑定方式与转换类 Pipes 略有不同,详见后续“绑定验证 Pipes”章节。更多验证技巧可参考官方“Validation techniques”文档。四、自定义 Pipes(Custom pipes)尽管 Nest 提供了完善的内置 Pipes,但可通过自定义 Pipes 实现个性化需求。自定义 Pipes 需实现 PipeTransform 接口,并编写 transform() 方法。4.1 自定义 Pipe 基础结构所有自定义 Pipes 必须实现 transform() 方法,该方法接收两个参数:value:当前处理的方法参数(路由处理器接收前的值);metadata:当前处理参数的元数据,描述参数的类型、元类型等信息。基础示例:空验证 Pipe(Identity Function)// validation.pipe.ts import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ValidationPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { return value; // 原样返回参数,无任何处理 } }提示:PipeTransform<T, R> 是泛型接口,T 表示输入值类型,R 表示 transform() 方法的返回值类型。4.2 参数元数据(ArgumentMetadata)metadata 参数是 ArgumentMetadata 类型的对象,包含以下属性:export interface ArgumentMetadata { type: 'body' | 'query' | 'param' | 'custom'; // 参数类型(body/query/param/自定义) metatype?: Type<unknown>; // 参数的元类型(如 String、Number,未声明类型则为 undefined) data?: string; // 装饰器中传入的字符串(如 @Body('name') 中的 'name',空则为 undefined) }警告:TypeScript 接口在转译时会消失,若方法参数类型声明为接口(而非类),则 metatype 值为 Object。五、基于 Schema 的验证(Schema based validation)通过 Schema 定义参数规则,实现统一、可复用的验证逻辑,常用 Zod 库实现(Schema 简洁、API 易读)。5.1 基于 Zod 的自定义验证 Pipe步骤1:安装依赖$ npm install --save zod步骤2:实现 ZodValidationPipeimport { PipeTransform, ArgumentMetadata, BadRequestException } from '@nestjs/common'; import { ZodSchema } from 'zod'; export class ZodValidationPipe implements PipeTransform { constructor(private schema: ZodSchema) {} // 接收 Zod Schema 作为参数 transform(value: unknown, metadata: ArgumentMetadata) { try { const parsedValue = this.schema.parse(value); // 校验参数 return parsedValue; // 校验通过,返回解析后的值 } catch (error) { throw new BadRequestException('Validation failed'); // 校验失败,抛出异常 } } }5.2 绑定 Zod 验证 Pipe步骤1:定义 Zod Schema 和 DTO 类型import { z } from 'zod'; // 定义 Zod Schema export const createCatSchema = z .object({ name: z.string(), age: z.number(), breed: z.string(), }) .required(); // 从 Schema 推导 DTO 类型 export type CreateCatDto = z.infer<typeof createCatSchema>;步骤2:通过 @UsePipes() 绑定到方法// cats.controller.ts import { Post, UsePipes, Body } from '@nestjs/common'; import { ZodValidationPipe } from './zod-validation.pipe'; import { createCatSchema, CreateCatDto } from './dto/create-cat.dto'; @Post() @UsePipes(new ZodValidationPipe(createCatSchema)) // 传入 Schema 实例化 Pipe async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); }提示:@UsePipes() 装饰器从 @nestjs/common 导入。警告:zod 库要求在 tsconfig.json 文件中启用 strictNullChecks 配置。六、基于 Class Validator 的验证(Class validator)结合 class-validator 库,使用装饰器实现验证逻辑,让 DTO 类成为参数验证的唯一数据源(无需单独定义 Schema)。该方式仅支持 TypeScript,不支持原生 JavaScript。6.1 实现步骤步骤1:安装依赖$ npm i --save class-validator class-transformer步骤2:给 DTO 类添加验证装饰器// create-cat.dto.ts import { IsString, IsInt } from 'class-validator'; export class CreateCatDto { @IsString() // 验证 name 为字符串 name: string; @IsInt() // 验证 age 为整数 age: number; @IsString() // 验证 breed 为字符串 breed: string; }提示:更多 class-validator 装饰器用法,可参考其官方文档。步骤3:实现 ValidationPipe// validation.pipe.ts import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; import { validate } from 'class-validator'; import { plainToInstance } from 'class-transformer'; @Injectable() export class ValidationPipe implements PipeTransform<any> { async transform(value: any, { metatype }: ArgumentMetadata) { // 跳过原生 JavaScript 类型的参数(无验证装饰器) if (!metatype || !this.toValidate(metatype)) { return value; } // 将普通对象转换为带类型的实例(让 class-validator 能识别装饰器) const object = plainToInstance(metatype, value); // 执行验证 const errors = await validate(object); // 验证失败,抛出异常 if (errors.length > 0) { throw new BadRequestException('Validation failed'); } // 验证通过,原样返回 return value; } // 辅助方法:判断是否需要验证(排除原生 JavaScript 类型) private toValidate(metatype: Function): boolean { const types: Function[] = [String, Boolean, Number, Array, Object]; return !types.includes(metatype); } }提示:Nest 已内置 ValidationPipe,功能比自定义示例更完善,本示例仅用于演示自定义验证 Pipe 的实现逻辑。内置 ValidationPipe 的详细用法可参考官方文档。说明:class-transformer 库与 class-validator 同属一个作者,兼容性极佳,用于将普通对象转换为带类型的实例,让验证装饰器生效。步骤4:绑定 ValidationPipe可绑定到参数级(仅验证单个参数),也支持方法级、控制器级、全局级绑定:// cats.controller.ts @Post() async create( @Body(new ValidationPipe()) createCatDto: CreateCatDto, // 参数级绑定 ) { this.catsService.create(createCatDto); }参数级绑定适用于验证逻辑仅针对单个参数的场景。七、全局级 Pipes(Global scoped pipes)通用的 Pipes(如 ValidationPipe)可设置为全局级,作用于整个应用的所有控制器和路由处理器,无需重复绑定。7.1 方式1:main.ts 中使用 useGlobalPipes()// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from './validation.pipe'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); // 注册全局 Pipe await app.listen(process.env.PORT ?? 3000); } bootstrap();注意:混合应用中,useGlobalPipes() 不会为网关和微服务注册 Pipes;非混合微服务应用中,useGlobalPipes() 会全局挂载 Pipes。缺点:该方式注册的全局 Pipe 脱离模块上下文,无法注入依赖。7.2 方式2:模块中使用 APP_PIPE 令牌(推荐)通过模块 providers 注册,支持依赖注入,注册后仍为全局生效:// app.module.ts import { Module } from '@nestjs/common'; import { APP_PIPE } from '@nestjs/core'; import { ValidationPipe } from './validation.pipe'; @Module({ providers: [ { provide: APP_PIPE, useClass: ValidationPipe, // 传入全局 Pipe 类 }, ], }) export class AppModule {}提示:建议在 Pipe 所在的模块中配置;可注册多个全局 Pipe,依次添加到 providers 数组即可;useClass 并非唯一的自定义提供者注册方式,可参考官方文档了解更多。八、转换场景实战(Transformation use case)转换类 Pipes 的核心作用是修改输入参数,使其符合路由处理器的期望格式(返回值会完全覆盖原参数值),常见场景:类型转换、默认值填充、参数预处理等。8.1 示例1:自定义 ParseIntPipe以下为简化版 ParseIntPipe(Nest 内置版本更完善),演示转换类 Pipe 的实现逻辑:// parse-int.pipe.ts import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; @Injectable() export class ParseIntPipe implements PipeTransform<string, number> { transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10); // 将字符串转换为整数 if (isNaN(val)) { // 转换失败,抛出异常 throw new BadRequestException('Validation failed'); } return val; // 转换成功,返回整数 } }绑定使用:@Get(':id') async findOne(@Param('id', new ParseIntPipe()) id) { return this.catsService.findOne(id); }8.2 示例2:根据 ID 查询实体通过 Pipe 抽象“根据 ID 查询实体”的逻辑,减少路由处理器的重复代码:@Get(':id') findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) { return userEntity; // Pipe 已返回查询到的实体,直接返回 }说明:UserByIdPipe 接收 id 作为输入,查询数据库并返回 UserEntity 实例,实现代码可自行扩展。九、默认值填充(Providing defaults)Parse 系列 Pipes 要求参数必须存在(null/undefined 会抛出异常),若需处理参数缺失的场景,可使用 DefaultValuePipe 先填充默认值,再由 Parse Pipe 处理。示例:默认值 + Parse* Pipe 组合使用@Get() async findAll( // 默认值为 false,再转换为布尔值 @Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean, // 默认值为 0,再转换为整数 @Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number, ) { return this.catsService.findAll({ activeOnly, page }); }Guards一、Guards 核心概念Guard 是一个使用 @Injectable() 装饰器注解、并实现 CanActivate 接口的类,核心职责是根据运行时的特定条件(如权限、角色、访问控制列表等),决定请求是否能被路由处理器处理,这一过程通常被称为“授权”。在传统 Express 应用中,授权(及与其协作的认证)通常由中间件处理。中间件适合处理认证(如令牌验证、给请求对象附加属性),因为这些操作与特定路由上下文及其元数据关联不强;但中间件本身“无感知”,不知道调用 next() 后会执行哪个处理器。与中间件不同,Guards 可访问 ExecutionContext 实例,能精确知晓接下来要执行的内容。它与异常过滤器、管道、拦截器类似,可在请求/响应周期的正确节点插入处理逻辑,且支持声明式使用,能保持代码简洁、复用(DRY 原则)。提示:Guards 在所有中间件执行完毕后运行,但在任何拦截器或管道之前执行。二、授权 Guard(Authorization guard)授权是 Guards 的典型应用场景——特定路由仅允许具有足够权限的调用者(通常是已认证用户)访问。下面实现的 AuthGuard 假设用户已认证(请求头中包含令牌),其核心逻辑是提取并验证令牌,根据验证结果决定请求是否可继续。2.1 AuthGuard 基础实现// auth.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class AuthGuard implements AuthGuard { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); return validateRequest(request); // 自定义令牌验证逻辑 } }提示:若需真实场景的认证机制实现示例,可参考官方对应章节;如需更复杂的授权示例,可查看官方相关文档。2.2 canActivate() 方法核心说明所有 Guards 必须实现 canActivate() 方法,该方法返回一个布尔值(或 Promise、Observable),用于控制请求的后续流程:返回 true:请求将被正常处理(路由处理器执行);返回 false:Nest 将拒绝请求,阻止路由处理器执行。注:validateRequest() 函数的逻辑可根据需求灵活设计,示例重点展示 Guards 在请求周期中的作用。三、执行上下文(Execution context)canActivate() 方法接收唯一参数——ExecutionContext 实例,该实例继承自 ArgumentsHost(在异常过滤器章节中已介绍)。示例中通过 context.switchToHttp().getRequest() 获取请求对象,与异常过滤器中使用 ArgumentsHost 的方式一致。除了继承 ArgumentsHost 的辅助方法,ExecutionContext 还新增了多个辅助方法,可提供当前执行过程的更多细节,有助于构建可跨多个控制器、方法和执行上下文的通用 Guards。更多关于 ExecutionContext 的用法,可参考官方文档。AuthGuard:NestJS 的 “路由安全门卫”,用于接口的认证 / 授权,在请求到达 Controller 前验证权限,支持局部 / 全局使用;Observable:RxJS 的 “异步数据流容器”,用于处理多值、可取消的异步场景,是 NestJS 处理实时数据、微服务的核心方式,比 Promise 更灵活。四、基于角色的认证(Role-based authentication)下面实现一个更实用的 Guards——仅允许具有特定角色的用户访问路由。先从基础模板开始,后续逐步完善功能(当前默认允许所有请求)。4.1 RolesGuard 基础模板// roles.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class RolesGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { return true; // 暂时允许所有请求 } }五、绑定 Guards(Binding guards)与管道、异常过滤器类似,Guards 支持三种作用域:控制器级、方法级、全局级。通过 @UseGuards() 装饰器绑定,该装饰器可接收单个参数或多个逗号分隔的参数,方便一次性应用多个 Guards。5.1 控制器级绑定作用于控制器内所有路由处理器:import { Controller, UseGuards } from '@nestjs/common'; import { RolesGuard } from './roles.guard'; @Controller('cats') @UseGuards(RolesGuard) // 绑定到整个控制器 export class CatsController {}提示:@UseGuards() 装饰器从 @nestjs/common 导入。两种绑定方式传入类(推荐):如上述示例,由框架负责实例化,支持依赖注入;传入实例:直接实例化 Guards,适用于需要自定义配置的场景: `@Controller('cats') @UseGuards(new RolesGuard()) // 传入实例 export class CatsController {} `5.2 方法级绑定仅作用于单个路由处理器,在方法上添加 @UseGuards() 装饰器即可:@Controller('cats') export class CatsController { @Post() @UseGuards(RolesGuard) // 仅绑定到 create 方法 async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } }5.3 全局级绑定(Global scoped guards)作用于整个应用的所有控制器和路由处理器,有两种实现方式。方式1:main.ts 中使用 useGlobalGuards()// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { RolesGuard } from './roles.guard'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalGuards(new RolesGuard()); // 注册全局 Guard await app.listen(process.env.PORT ?? 3000); } bootstrap();注意:混合应用中,useGlobalGuards() 默认不会为网关和微服务注册 Guards(可参考官方“混合应用”章节修改此行为);非混合微服务应用中,useGlobalGuards() 会全局挂载 Guards。缺点:该方式注册的全局 Guard 脱离模块上下文,无法注入依赖。方式2:模块中使用 APP_GUARD 令牌(推荐)通过模块 providers 注册,支持依赖注入,注册后仍为全局生效:// app.module.ts import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { RolesGuard } from './roles.guard'; @Module({ providers: [ { provide: APP_GUARD, useClass: RolesGuard, // 传入全局 Guard 类 }, ], }) export class AppModule {}提示:建议在 Guard 所在的模块中配置;可注册多个全局 Guard,依次添加到 providers 数组即可;useClass 并非唯一的自定义提供者注册方式,可参考官方文档了解更多。六、为处理器设置角色(Setting roles per handler)当前的 RolesGuard 仅能固定允许/拒绝请求,无法根据不同路由的角色要求灵活控制。Nest 提供自定义元数据功能,可通过 Reflector.createDecorator 静态方法创建装饰器,或使用内置 @SetMetadata() 装饰器,将角色信息附加到路由处理器上。6.1 方式1:使用 Reflector 创建 @Roles() 装饰器Reflector 是 Nest 内置工具类,从 @nestjs/core 导出,用于创建自定义元数据装饰器:// roles.decorator.ts import { Reflector } from '@nestjs/core'; // 创建 Roles 装饰器,接收字符串数组(角色列表) export const Roles = Reflector.createDecorator<string[]>();6.2 为路由处理器附加角色元数据使用 @Roles() 装饰器标注路由,指定该路由允许的角色:// cats.controller.ts import { Post, UseGuards, Body } from '@nestjs/common'; import { Roles } from './roles.decorator'; import { CreateCatDto } from './dto/create-cat.dto'; @Post() @Roles(['admin']) // 仅允许 admin 角色访问该接口 async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); }6.3 方式2:使用内置 @SetMetadata() 装饰器除了自定义装饰器,也可直接使用 Nest 内置的 @SetMetadata() 装饰器附加元数据,具体用法可参考官方文档。七、整合实现 RolesGuard(Putting it all together)完善 RolesGuard 逻辑:通过 Reflector 提取当前路由的角色元数据,对比当前用户的角色与路由要求的角色,根据匹配结果决定是否允许请求。完整实现// roles.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Roles } from './roles.decorator'; @Injectable() export class RolesGuard implements CanActivate { // 注入 Reflector,用于提取元数据 constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { // 提取当前路由处理器的角色元数据(通过 Roles 装饰器附加) const roles = this.reflector.get(Roles, context.getHandler()); // 若未设置角色元数据,默认允许请求 if (!roles) { return true; } // 获取当前请求对象,提取用户信息(假设 request.user 包含用户角色) const request = context.switchToHttp().getRequest(); const user = request.user; // 对比用户角色与路由要求的角色(matchRoles 为自定义逻辑) return matchRoles(roles, user.roles); } }提示:在 Node.js 生态中,通常会将已认证用户信息附加到 request.user 上。实际应用中,可在自定义认证 Guard(或中间件)中实现这一关联,更多细节可参考官方认证相关章节。警告:matchRoles() 函数的逻辑可根据需求灵活设计(如精确匹配、包含匹配等),示例重点展示 Guards 如何结合元数据实现角色授权。提示:如需更详细的元数据提取用法,可参考官方“执行上下文”章节中的“反射与元数据”部分。八、Guards 异常处理默认异常响应当用户权限不足(Guards 返回 false)时,Nest 会自动返回如下响应:{ "statusCode": 403, "message": "Forbidden resource", "error": "Forbidden" }底层逻辑:当 Guards 返回 false 时,框架会抛出 ForbiddenException。自定义异常响应若需返回自定义错误,可在 Guards 中直接抛出特定异常(如未授权异常):import { UnauthorizedException } from '@nestjs/common'; // 在 canActivate 方法中抛出异常 throw new UnauthorizedException();注:Guards 中抛出的任何异常,都会由异常层(全局异常过滤器和当前上下文应用的任何异常过滤器)处理。提示:若需真实场景的授权实现示例,可参考官方对应章节。Interceptors一、Interceptors 核心概念Interceptor(拦截器)是一个使用 @Injectable() 装饰器注解、并实现 NestInterceptor 接口的类,其设计灵感来源于面向切面编程(AOP)技术,核心作用是在路由处理器执行的前后插入自定义逻辑,对请求/响应流进行灵活控制和处理。Interceptors 的核心能力在方法执行前/后绑定额外逻辑;转换函数返回的结果(响应数据);转换函数抛出的异常;扩展基础函数的行为;根据特定条件完全重写函数(如实现缓存功能)。二、基础用法(Basics)所有拦截器必须实现 intercept() 方法,该方法接收两个核心参数,构成拦截器的核心工作流。2.1 intercept() 方法参数ExecutionContext(执行上下文):与 Guards 中的 ExecutionContext 完全一致,继承自 ArgumentsHost,封装了当前请求的执行信息(如请求对象、路由处理器元数据等)。 ArgumentsHost 是一个包装原始处理器参数的工具类,会根据应用类型(HTTP、微服务等)提供不同的参数数组,详细用法可参考异常过滤器章节中的相关内容。CallHandler(调用处理器):实现了 handle() 方法,用于触发路由处理器的执行。若在 intercept() 方法中不调用 handle(),则路由处理器将完全不会执行。2.2 核心工作原理intercept() 方法本质上是对请求/响应流的“包裹”,因此可以在路由处理器执行前后分别实现自定义逻辑:在调用 next.handle() 之前编写的代码,会在路由处理器执行前执行;next.handle() 会返回一个 RxJS Observable,该流包含路由处理器的返回结果,通过 RxJS 操作符可对响应流进行后续处理(如转换、异常捕获),实现“执行后”的逻辑。提示:在面向切面编程(AOP)术语中,调用 next.handle()(即触发路由处理器执行)的操作称为“切入点(Pointcut)”,是插入额外逻辑的关键节点。2.3 示例说明以 POST /cats 请求为例:该请求最终会触发 CatsController 中的 create() 方法。若拦截器中未调用 handle(),则 create() 方法不会执行;一旦调用 handle() 并返回其 Observable,create() 方法会被触发,且可通过 Observable 操作符对响应流进行后续处理。三、执行上下文(Execution context)ExecutionContext 继承自 ArgumentsHost,除了拥有 ArgumentsHost 的所有辅助方法(如获取请求对象),还新增了多个辅助方法,可提供当前执行过程的更多细节(如当前路由处理器、控制器信息)。这些方法有助于构建通用型拦截器,使其能够跨多个控制器、方法和执行上下文(如 HTTP、微服务)工作。更多关于 ExecutionContext 的用法,可参考官方文档。四、调用处理器(Call handler)CallHandler 的核心方法是 handle(),其核心作用是触发后续的路由处理器执行,并返回一个 RxJS Observable,该 Observable 包含路由处理器的返回结果(响应数据)或抛出的异常。通过 handle() 返回的 Observable,可借助 RxJS 丰富的操作符(如 tap、map、catchError)对响应流进行灵活处理,这是拦截器实现“转换响应、捕获异常”等能力的核心基础。五、切面拦截(Aspect interception)切面拦截是拦截器的典型应用场景之一,例如记录用户交互(存储用户调用记录、异步分发事件、计算请求耗时等)。以下是一个简单的 LoggingInterceptor(日志拦截器),演示拦截器的基础实现。5.1 LoggingInterceptor 实现// logging.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { console.log('Before...'); // 路由处理器执行前的逻辑 const now = Date.now(); return next .handle() .pipe( // tap 操作符:监听流的终止(正常/异常),不干扰响应流 tap(() => console.log(`After... ${Date.now() - now}ms`)), // 路由处理器执行后的逻辑 ); } }提示:NestInterceptor<T, R> 是泛型接口,T 表示 Observable 的类型(支持响应流),R 表示 Observable 包裹的值的类型。说明:拦截器与控制器、提供者、Guards 等一样,可通过构造函数注入依赖。5.2 关键操作符说明示例中使用了 RxJS 的 tap() 操作符,其作用是:在 Observable 流正常终止或异常终止时,执行自定义逻辑(如日志打印),但不会修改或干扰响应流本身,适合用于记录日志、统计耗时等场景。六、绑定 Interceptors(Binding interceptors)与 Pipes、Guards 类似,拦截器支持三种作用域:控制器级、方法级、全局级。通过 @UseInterceptors() 装饰器绑定,该装饰器可接收单个参数或多个逗号分隔的参数,方便一次性应用多个拦截器。提示:@UseInterceptors() 装饰器从 @nestjs/common 导入。6.1 控制器级绑定作用于控制器内所有路由处理器:// cats.controller.ts import { Controller, UseInterceptors } from '@nestjs/common'; import { LoggingInterceptor } from './logging.interceptor'; @Controller('cats') @UseInterceptors(LoggingInterceptor) // 绑定到整个控制器 export class CatsController {}效果:调用 CatsController 中的任何路由(如 GET /cats、POST /cats),都会触发 LoggingInterceptor 的逻辑,控制台会输出:Before... After... 1ms两种绑定方式传入类(推荐):如上述示例,由框架负责实例化,支持依赖注入;传入实例:直接实例化拦截器,适用于需要自定义配置的场景: `@Controller('cats') @UseInterceptors(new LoggingInterceptor()) // 传入实例 export class CatsController {} `6.2 方法级绑定仅作用于单个路由处理器,在方法上添加 @UseInterceptors() 装饰器即可:@Controller('cats') export class CatsController { @Get() @UseInterceptors(LoggingInterceptor) // 仅绑定到 findAll 方法 async findAll() { return this.catsService.findAll(); } }6.3 全局级绑定(Global scoped interceptors)作用于整个应用的所有控制器和路由处理器,有两种实现方式。方式1:main.ts 中使用 useGlobalInterceptors()// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { LoggingInterceptor } from './logging.interceptor'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalInterceptors(new LoggingInterceptor()); // 注册全局拦截器 await app.listen(process.env.PORT ?? 3000); } bootstrap();方式2:模块中使用 APP_INTERCEPTOR 令牌(推荐)通过模块 providers 注册,支持依赖注入,注册后仍为全局生效。解决了方式1“无法注入依赖”的问题(方式1注册的全局拦截器脱离模块上下文):// app.module.ts import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { LoggingInterceptor } from './logging.interceptor'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor, // 传入全局拦截器类 }, ], }) export class AppModule {}提示:建议在拦截器所在的模块中配置;可注册多个全局拦截器,依次添加到 providers 数组即可;useClass 并非唯一的自定义提供者注册方式,可参考官方文档了解更多。七、响应映射(Response mapping)由于 next.handle() 返回 RxJS Observable,可借助 map() 操作符轻松修改响应流中的数据,实现响应格式的统一转换(如给所有响应包裹一层固定结构)。警告:响应映射功能不支持库特定的响应策略(禁止直接使用 @Res() 对象)。7.1 TransformInterceptor 实现(统一响应格式)该拦截器将路由处理器返回的结果,包裹到 { data: T } 结构中,实现全局响应格式统一:// transform.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; // 定义统一响应格式的接口 export interface Response<T> { data: T; } @Injectable() export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> { // map 操作符:修改响应流中的数据 return next.handle().pipe(map(data => ({ data }))); } }7.2 效果演示若路由处理器返回空数组 [](如 GET /cats),经过拦截器处理后,客户端收到的响应将是:{ "data": [] }提示:Nest 拦截器同时支持同步和异步的 intercept() 方法,必要时可直接将方法改为 async。7.3 示例2:空值转换将响应中的所有 null 值转换为空字符串 '',绑定全局后可作用于所有接口:import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class ExcludeNullInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next .handle() .pipe(map(value => value === null ? '' : value )); // 空值转换逻辑 } }八、异常映射(Exception mapping)借助 RxJS 的 catchError()操作符,可捕获路由处理器抛出的异常,并将其转换为自定义异常,实现全局异常统一处理。8.1 ErrorsInterceptor 实现(异常转换)// errors.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, BadGatewayException, CallHandler, } from '@nestjs/common'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Injectable() export class ErrorsInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next .handle() .pipe( // catchError 操作符:捕获流中的异常并转换 catchError(err => throwError(() => new BadGatewayException())), ); } }效果:无论路由处理器抛出何种异常,都会被捕获并转换为 BadGatewayException(状态码 502)。九、流重写(Stream overriding)拦截器可根据特定条件,完全阻止路由处理器的执行,直接返回自定义的响应流(如实现缓存功能)。核心是:不调用 next.handle(),而是返回一个新的 Observable。9.1 CacheInterceptor 实现(基础缓存)以下是一个简单的缓存拦截器示例(实际场景需考虑缓存过期时间、缓存失效、缓存大小等因素):// cache.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, of } from 'rxjs'; @Injectable() export class CacheInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const isCached = true; // 模拟缓存存在(实际场景需判断缓存是否有效) if (isCached) { return of([]); // 返回自定义响应流,不执行路由处理器 } return next.handle(); // 缓存不存在,执行路由处理器并返回其响应流 } }关键说明使用 RxJS 的 of() 操作符创建新的 Observable,返回固定响应 [];当 isCached = true 时,不调用 next.handle(),路由处理器不会执行,直接返回缓存数据;若要实现通用缓存方案,可结合 Reflector(参考 Guards 章节)和自定义装饰器,为不同路由配置不同缓存规则。十、更多操作符应用(More operators)借助 RxJS 丰富的操作符,可实现更多实用功能。以下示例实现“请求超时处理”:当路由处理器在指定时间内未返回响应,自动终止并抛出超时异常。10.1 TimeoutInterceptor 实现(请求超时)// timeout.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common'; import { Observable, throwError, TimeoutError } from 'rxjs'; import { catchError, timeout } from 'rxjs/operators'; @Injectable() export class TimeoutInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( timeout(5000), // 设置超时时间:5秒 catchError(err => { // 捕获超时异常,转换为 RequestTimeoutException(状态码 408) if (err instanceof TimeoutError) { return throwError(() => new RequestTimeoutException()); } // 非超时异常,原样抛出 return throwError(() => err); }), ); }; };效果说明若路由处理器执行时间超过 5 秒,请求会被自动终止,客户端收到 408(Request Timeout)响应。可在抛出异常前添加自定义逻辑(如释放资源、记录超时日志)。Custom decorators一、装饰器核心基础Nest 框架的核心构建基于 装饰器(Decorators) 这一语言特性。装饰器在许多常用编程语言中是成熟的概念,但在 JavaScript 领域仍相对较新。1.1 装饰器定义(ES2016 标准)ES2016 装饰器是一个返回函数的表达式,可接收三个参数:目标(target)、名称(name)和属性描述符(property descriptor)。使用方式:在需要装饰的对象(类、方法、属性)顶部,添加 @ 前缀 + 装饰器名称。装饰器可用于修饰类、方法或属性。提示:若需深入理解装饰器工作原理,建议参考官方推荐的相关文章。二、参数装饰器(Param decorators)Nest 提供了一组实用的内置参数装饰器,可直接用于 HTTP 路由处理器中,用于快速提取请求中的相关数据,对应底层 Express(或 Fastify)的请求对象。2.1 内置参数装饰器列表以下是 Nest 内置参数装饰器及其对应的 Express/Fastify 对象:Nest 装饰器对应底层对象@Request()、@Req()req(完整请求对象)@Response()、@Res()res(完整响应对象)@Next()next(下一步中间件函数)@Session()req.session(请求会话对象)@Param(param?: string)req.params(路由参数对象);传入参数名时,获取 req.params[param]@Body(param?: string)req.body(请求体对象);传入参数名时,获取 req.body[param]@Query(param?: string)req.query(查询参数对象);传入参数名时,获取 req.query[param]@Headers(param?: string)req.headers(请求头对象);传入参数名时,获取 req.headers[param]@Ip()req.ip(请求客户端 IP 地址)@HostParam()req.hosts(请求主机信息)2.2 自定义参数装饰器的意义在 Node.js 生态中,常见做法是将自定义属性附加到 req 对象上(如认证后的用户信息 req.user),此时需要在每个路由处理器中手动提取:const user = req.user;自定义参数装饰器可将这一重复操作封装,提升代码可读性和复用性,可在所有控制器中统一复用。三、自定义参数装饰器实现Nest 提供 createParamDecorator()方法,用于创建自定义参数装饰器,该方法接收两个参数:装饰器工厂函数、执行上下文(ExecutionContext)。3.1 基础实现(提取 req.user)创建 @User() 装饰器,用于快速提取请求对象中的 user 属性:// user.decorator.ts import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { // 切换到 HTTP 上下文,获取请求对象 const request = ctx.switchToHttp().getRequest(); // 返回 req.user 属性 return request.user; }, );3.2 装饰器使用方式在控制器路由处理器中,直接使用 @User() 装饰器即可提取用户信息:@Get() async findOne(@User() user: UserEntity) { console.log(user); // 直接获取 req.user }四、传递数据到装饰器(Passing data)当装饰器的行为需要根据特定条件调整时,可通过 data 参数(装饰器工厂函数的第一个参数),向装饰器传递自定义参数。典型场景:创建可根据属性名,提取 req.user 中特定字段的自定义装饰器。4.1 场景假设认证层验证请求后,会在 req.user 中附加用户实体,格式如下:{ "id": 101, "firstName": "Alan", "lastName": "Turing", "email": "alan@email.com", "roles": ["admin"] }4.2 带参数的装饰器实现修改 @User() 装饰器,支持传入属性名,提取对应字段(若未传入字段名,则返回完整 user 对象):// user.decorator.ts import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator( (data: string, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const user = request.user; // 若传入 data(属性名),则返回 user[data],否则返回完整 user 对象 return data ? user?.[data] : user; }, );4.3 带参数的装饰器使用传入属性名,即可提取 req.user 中的特定字段,提升代码可读性:@Get() async findOne(@User('firstName') firstName: string) { console.log(`Hello ${firstName}`); // 输出:Hello Alan }可通过传入不同属性名,提取 user 对象中的不同字段;若 user 对象结构复杂,该方式可大幅简化路由处理器代码。提示:TypeScript 用户可利用 createParamDecorator<T>() 泛型确保类型安全。例如 createParamDecorator<string>((data, ctx) => ...),或在工厂函数中指定 data: string;若两者都省略,data 类型将为 any。五、与管道配合使用(Working with pipes)Nest 会将自定义参数装饰器与内置参数装饰器(@Body()、@Param()、@Query())同等对待,这意味着管道(Pipes)也会对自定义装饰器标注的参数执行验证/转换逻辑。此外,可直接将管道应用到自定义参数装饰器上,需注意设置 validateCustomDecorators: true(默认不验证自定义装饰器标注的参数)。5.1 示例:结合 ValidationPipe@Get() async findOne( // 将 ValidationPipe 应用到 @User() 装饰器,开启自定义装饰器验证 @User(new ValidationPipe({ validateCustomDecorators: true })) user: UserEntity, ) { console.log(user); }提示:必须设置 validateCustomDecorators: true,否则 ValidationPipe 不会验证自定义装饰器标注的参数。六、装饰器组合(Decorator composition)Nest 提供 applyDecorators() 辅助方法,用于将多个装饰器组合成一个单一装饰器,适用于将一组相关装饰器(如认证、授权相关)封装复用。6.1 组合装饰器实现创建 @Auth() 装饰器,组合元数据设置、Guard 应用、Swagger 文档注解等多个装饰器:// auth.decorator.ts import { applyDecorators } from '@nestjs/common'; export function Auth(...roles: Role[]) { return applyDecorators( SetMetadata('roles', roles), // 设置角色元数据 UseGuards(AuthGuard, RolesGuard), // 应用认证、角色守卫 ApiBearerAuth(), // Swagger Bearer 认证注解 ApiUnauthorizedResponse({ description: 'Unauthorized' }), // Swagger 未授权响应注解 ); }6.2 组合装饰器使用使用单一 @Auth() 装饰器,即可应用所有组合的装饰器,简化代码:@Get('users') @Auth('admin') // 仅需一行,应用所有组合的装饰器 findAllUsers() {}警告:@nestjs/swagger 包中的 @ApiHideProperty() 装饰器不可组合,与 applyDecorators() 函数配合使用时会失效。简单记:@ApiHideProperty() 别放进 applyDecorators(),要么单独用,要么换 @ApiProperty({ hidden: true }) 组合。
2026年02月16日
10 阅读
0 评论
4 点赞