【Nest.js 通关秘籍 - 基础篇】带你轻松掌握后端开发

前言

juejin.cn/book/722698...

bosombaby.blog.csdn.net/article/det...

  1. Express 进一步封装的企业级框架,对标 Java
  2. 国外远程工作、外包接单、独立开发自己产品
  3. 全栈开发、竞争力提升、学习优秀的架构设计
  4. 后续强类型后端语言统一使用 IDEA 进行开发

基础概念

  1. entity:对应数据库表的实体
  2. handler:控制器里处理路由的方法
  3. controller:控制器,用于处理路由,解析请求参数
  4. service:实现业务逻辑的地方,比如操作数据库等
  5. dtodata transfer object,数据传输对象,可校验数据格式,请求 => dto => 参数 => controller
  6. module :模块,包含 controllerservice 等,比如用户模块、书籍模块。其中:
    • controllers 只能被注入,使用 @Controller 声明
    • providers 可以注入也能被注入,使用 @Injectable 声明
    • exports 暴露给外部模块使用
    • 同时也可以使用 imports 注入其他模块逻辑代码
    • 被大量模块依赖时使用 @Global 装饰器,这样不导入也可用
  7. iocInverse of Control,反转控制或者叫依赖注入,只要声明依赖,运行时 Nest 会自动注入依赖的实例
  8. aopAspect Oriented Programming 面向切面编程,在多个请求响应流程中可以复用的逻辑,比如日志记录等,具体包含:
    • middleware
    • interceptor
    • guard
    • exception filter
    • pipe

Nest CLI

  • nest new 快速创建项目
  • nest generate 快速生成各种代码
    • module
    • controller
    • middleware
    • guard
    • interceptor
    • pipe
    • decorator
    • service
    • resource
    • --no-spec 无测试文件
  • nest build 使用 tsc 或者 webpack 构建代码
  • nest start 启动开发服务,支持 watch 和调试
  • nest info 打印 nodenpmnest 包的依赖版本

HTTP 数据传输

我们用 axios 发送请求,使用 Nest 承接后端服务,实现了 5 种 http/https 的数据传输方式:

  1. 其中前两种是 url 中的:
    • url paramurl 中的参数,Nest 中使用 @Param 来取
    • queryurl? 后的字符串,Nest 中使用 @Query 来取
  2. 后三种是 body 中的:
    • form urlencoded :类似 query 字符串,只不过是放在 body 中。Nest 中使用 @Body 来取,axios 中需要指定 content type 为 application/x-www-form-urlencoded,并且对数据用 qs 或者 query-string 库做 url encode`
    • jsonjson 格式的数据。Nest 中使用 @Body 来取,axios 中不需要单独指定 content type,axios 内部会处理,项目中较为常用
    • form data :通过 ----- 作为 boundary 分隔的数据。主要用于传输文件,Nest 中要使用 FilesInterceptor 来处理其中的 binary 字段,用 @UseInterceptors 来启用,其余字段用 @Body 来取。axios 中需要指定 content type 为 multipart/form-data,并且用 FormData 对象来封装传输的内容。

IOC(依赖注入)

  1. 不需要声明对象的实例,只需要在特定位置注入类,那么 Nest 会按照一定的规则自动进行依赖注入实例:
    1. 构造器注入:写在构造函数的依赖
    2. 属性注入:@Inject 写在属性上的依赖
    3. 两种属性一致,只是注入的时机不同

为什么会出现 IOC

javascript 复制代码
const config = new Config({ username: 'xxx', password: 'xxx'});

const dataSource = new DataSource(config);

const repository = new Repository(dataSource);

const service = new Service(repository);

const controller = new Controller(service);
  • 上述执行逻辑要按照一定的顺序才能执行最终的代码,人为梳理较为麻烦。
  • IOC 会按照模块的维度 module 实现一个存放对象的容器,扫描带有 @Controller@Injectable 装饰器的类,这些类根据依赖关系自动注入它所依赖的对象,然后将实例放到容器中。
  • 原本是自己手动创建,但后续改为被动等待 Nest 梳理,这就叫 反转控制

循环依赖

  1. Bbb 依赖 Aaa,但是 Aaa 又同时依赖 Bbb,此时 Bbb 还未处理完成,导致 Aaa 未完成,此时 Bbb 就是 undefinedimportsProvider 的原理一致。
  2. 可以利用 forwardRef 进行包裹,会先创建两个模块,然后再把引用转发给对方。

AOP(切面编程)

  • 跨多个 controller 的逻辑,Nest 封装了通用逻辑,可以在执行过程中复用代码逻辑。
  • 本质:请求的时候进行拦截,先执行公共的代码逻辑,然后放行,可以保持业务逻辑的纯粹性。

Middleware(中间件)

javascript 复制代码
app.use(function (req: Request, res: Response, next: NextFunction) {
  console.log('before request...', req.url);

  next();

  console.log('after request...');
});
  1. 全局中间件、路由中间件
  2. 访问前 => next => 访问后
  3. 全局中间件先触发,路由中间件执行,最后全局中间件结束

Guard(守卫)

javascript 复制代码
1. @UseGuards(LoginGuard);
2. app.useGlobalGuards(new LoginGuard());
  1. 穿透全局、路由前置中间件处理规则触发,也就是 next 之前判断,是否放行
  2. 可以单路由声明、全局声明,其中 guard 也可以注入别的模块的执行代码逻辑进行判断

Interceptor(拦截器)

  1. Interceptor 可以拿到调用的 controllerhandler,与中间件不同。
  2. 也具有单个路由启用、全局路由启用的功能。

Guard VS Interceptor

  • 功能定位
    • Guard 主要用于权限控制,决定是否允许请求继续执行。
    • Interceptor 主要用于在请求处理前后添加额外逻辑,例如日志记录或响应处理。
  • 执行顺序
    • GuardInterceptor 之前执行,如果 Guard 返回 false,则后续逻辑(包括 Interceptor 和控制器方法)都不会执行。
  • 应用场景
    • Guard 适合用于认证和授权。
    • Interceptor 适合用于日志记录、响应格式化等。

总结来说,GuardInterceptorNestJS 中分别用于控制访问权限和处理请求/响应逻辑,它们在请求处理流程中扮演不同的角色,可以根据具体需求选择使用。

Pipe(管道)

javascript 复制代码
@Injectable()
export class ValidatePipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
   if (Number.isNaN(parseInt(value))) {
      throw new BadRequestException(`参数 ${metadata.data} 不是数字`);
    }
    
    console.log('metadata:', metadata);

    return typeof value === 'number' ? value * 10 : parseInt(value) * 10;
  }
}
  1. 对参数进行校验和转换
  2. 类型如下:
    • ValidationPipe
    • ParseIntPipe
    • ParseBoolPipe
    • ParseArrayPipe
    • ParseUUIDPipe
    • DefaultValuePipe
    • ParseEnumPipe
    • ParseFloatPipe
    • ParseFilePipe

ExceptionFilter(异常处理)

javascript 复制代码
@Catch(BadRequestException)
export class TestFilter implements ExceptionFilter {
  catch(exception: BadRequestException, host: ArgumentsHost) {
    const response: Response = host.switchToHttp().getResponse();

    response.status(400).json({
      statusCode: 400,
      message: 'test: ' + exception.message,
    });
  }
}
  1. Nest 应用抛出异常时,捕获并进行对应的响应。

Nest 内置了很多 HTTP 相关的异常,都是 HttpException 的子类,也可以自己扩展:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

执行流程

  1. Middleware、Guard、Pipe、Interceptor、ExceptionFilter 都是AOP 思想的实现,只不过是不同执行位置的切面,都可以灵活运用在某个路由或全部路由
  1. 执行成功的回调顺序
  2. 全局中间件 => 子中间件 => 路由守卫(不成功就中断执行)
  3. 中间件执行结束
  4. 请求拦截器 => 处理请求参数 => 判断合理性(不合理中断)=> 相应拦截器
  5. 异常错误处理
    1. throw new BadRequestException 官方配置
    2. ExceptionFilter 自定义错误数据结构
  6. 中间件 nextguard 返回 true/falseinterceptor 抛出异常、pipe 参数校验错误、异常处理等都可以一定程度上对程序进行中断操作,具体还要看实际项目。

Provider 类型

javascript 复制代码
@Module({
  imports: [OtherModule],
  controllers: [AppController],
  providers: [
    // 1. 通过类注入
    AppService,

    // 2. 自定义token注入
    {
      provide: 'app_service',
      useClass: AppService,
    },

    // 3. 注入指定值
    {
      provide: 'person1',
      useValue: { name: 'zhangsan', age: 18 },
    },

    // 4. 注入动态值
    {
      provide: 'person2',
      useClass: AppService,
      useFactory: () => {
        return { name: 'lisi', age: 20 };
      },
    },

    // 5. 注入其他模块的服务
    // 支持异步服务,但会阻塞应用程序的启动
    {
      provide: 'person3',
      useFactory(
        person: { name: string; age: number },
        appService: AppService,
      ) {
        return {
          name: person.name,
          age: person.age,
          msg: appService.getHello(),
        };
      },

      inject: ['person1', AppService],
    },

    // 6. 指定别名
    {
      provide: 'person4',
      useExisting: 'person1',
    },
  ],
})

生命周期(支持异步)

程序启动流程

  1. 模块维度下递归解析依赖
    • 按照顺序进行依赖注入。
  2. 模块初始化
    • 调用模块内的 controllerprovideronModuleInit 方法。
    • 调用模块的 onModuleInit 方法。
  3. 应用启动
    • 初始化完成,调用模块内的 controllerprovideronApplicationBootstrap 方法。
    • 调用模块的 onApplicationBootstrap 方法。
  4. 监听网络端口
    • 开始处理请求。

程序关闭流程

  1. 模块销毁
    • 调用每个模块的 controllerprovideronModuleDestroy 方法。
    • 调用模块的 onModuleDestroy 方法。
  1. 应用关闭前准备

    • 调用每个模块的 controllerproviderbeforeApplicationShutdown 方法。
    • 调用模块的 beforeApplicationShutdown 方法,moduleRef 获取对应的 provider 服务,传递系统信号,可执行必要的清理工作。
  2. 停止监听网络端口

    • 停止处理请求。
  3. 应用关闭

    • 调用每个模块的 controllerprovideronApplicationShutdown 方法。
    • 调用模块的 onApplicationShutdown 方法。
  4. 停止进程

    • 完成所有关闭操作,结束程序运行。

装饰器

概念

TypeScript 装饰器:高级编程技巧

使用

javascript 复制代码
// 1. 参数装饰器
export const MyQuery = createParamDecorator(
  (key: string, ctx: ExecutionContext) => {
    const request: Request = ctx.switchToHttp().getRequest();
    return request.headers[key];
  },
);


// 2. 装饰器汇总
import { applyDecorators, Get, UseGuards, SetMetadata } from '@nestjs/common';
import { LoginGuard } from './login.guard';

export function Bbb(path, role) {
  return applyDecorators(
    Get(path),
    SetMetadata('bbb', role),
    UseGuards(LoginGuard),
  );
}
  • 定义:一种特殊的类型声明,一种方法可以注入 类、属性、方法、参数,以此来扩展程序功能
  • createParamDecorator 创建参数装饰器,通过 ExecutionContext 可以获取 reqeust、response,可以实现很多内置装饰器的功能,比如 @Query、@Headers 等装饰器,applyDecorators 可以把多个装饰器汇总到一起执行

汇总

  • @Module: 声明 Nest 模块
  • @Controller:声明模块里的 controller
  • @Injectable:声明模块里可以注入的 provider
  • @Inject:通过 token 手动指定注入的 provider,token 可以是 class 或者 string
  • @Optional:声明注入的 provider 是可选的,可以为空
  • @Global:声明全局模块
  • @Catch:声明 exception filter 处理的 exception 类型
  • @UseFilters:路由级别使用 exception filter
  • @UsePipes:路由级别使用 pipe
  • @UseInterceptors:路由级别使用 interceptor
  • @SetMetadata:在 class 或者 handler 上添加 metadata
  • @Get、@Post、@Put、@Delete、@Patch、@Options、@Head:声明 get、post、put、delete、patch、options、head 的请求方式
  • @Param:取出 url 中的参数,比如 /aaa/:id 中的 id
  • @Query: 取出 query 部分的参数,比如 /aaa?name=xx 中的 name
  • @Body:取出请求 body,通过 dto class 来接收
  • @Headers:取出某个或全部请求头
  • @Session:取出 session 对象,需要启用 express-session 中间件
  • @HostParm: 取出 host 里的参数
  • @Req、@Request:注入 request 对象
  • @Res、@Response:注入 response 对象,一旦注入了这个 Nest 就不会把返回值作为响应了,除非指定 passthrough 为true
  • @Next:注入调用下一个 handler 的 next 方法
  • @HttpCode: 修改响应的状态码
  • @Header:修改响应头
  • @Redirect:指定重定向的 url
  • @Render:指定渲染用的模版引擎

Reflctor 和 Metadata

javascript 复制代码
const obj = {
  name: 'Alice',
  age: 25
};

const handler = {
  get(target, property) {
    if (property in target) {
      return Reflect.get(target, property);
    }
    return 'Property does not exist';
  },
  set(target, property, value) {
    if (property === 'age' && value < 0) {
      throw new Error('Age cannot be negative');
    }
    return Reflect.set(target, property, value);
  },
  deleteProperty(target, property) {
    if (property === 'name') {
      return false; // 不允许删除 'name' 属性
    }
    return Reflect.deleteProperty(target, property);
  }
};

const proxy = new Proxy(obj, handler);

console.log(proxy.name); // 输出:Alice
console.log(proxy.gender); // 输出:Property does not exist

proxy.age = -10; // 抛出错误:Age cannot be negative

Reflect.deleteProperty(proxy, 'name'); // 返回 false,不允许删除 'name' 属性
  1. Reflector 针对对象属性的获取、删除、修改,可以操作原型、函数、构造函数、扩展等行为,Proxy 实现 handler 对属性 getsetdelete 的方法拦截,具体处理还是由 Reflector 完成。

  2. metadata 在类、方法、属性等上定义(添加)元数据。这个方法允许你在运行时为对象或其属性附加额外的信息,而不改变其数据结构。Nest 运行的时候根据元数据来实现依赖的扫描和对象的创建

  3. 详细讲解 Reflector VS Proxy 之间的关系(文章待出......)

深入浅出Typescript装饰器与Reflect元数据本文正在参加「金石计划」 装饰器是什么? 在js中,其实装饰器是 - 掘金

ExecutionContext

    1. 上述两个类可以校验不同场景下的服务,如:http、wx、rpc 等。
  1. ExecutionContext 多了获取类和方法是因为需要进行 metadata 的权限认知。

动态模块

场景:引入外部的 module 时,需要动态传递一些参数 options

  • register:用一次注册一次
  • forRoot:注册一次,用多次,在 AppModule 引入
  • forFeature:在 forRoot 的基础上,可以用 forFeature 传入局部配置,一般在具体模块下 imports

RxJS 库

RxJS:组织异步逻辑的库,observable 数据源产生数据后,经过系列的 operator 简化异步逻辑的编写。

  • tap:不修改响应数据,执行一些额外逻辑,比如记录日志、更新缓存等。
  • map:对响应数据做修改,一般都是改成 {code, data, message} 的格式。
  • catchError:在 exception filter 之前处理抛出的异常,可以记录或者抛出别的异常。
  • timeout:处理响应超时的情况,抛出一个 TimeoutError,配合 catchError 可以返回超时的响应。
vue 复制代码
import { Contains, IsDate, IsEmail, IsFQDN, IsInt, Length, Max, Min } from 'class-validator';

export class Ppp {
    @Length(10, 20)
    title: string;
  
    @Contains('hello')
    text: string;
  
    @IsInt()
    @Min(0)
    @Max(10)
    rating: number;
  
    @IsEmail()
    email: string;
  
    @IsFQDN()
    site: string;
}

DTO 数据校验

  • class-transformer 将普通对象转为 class 实例。
  • class-validator 包提供基于装饰器声明的规则进行对象校验。
  • 声明参数类型 DTO 类 => pipe 获取类 => 把 POST 请求的参数对象通过 class-transformer 转为实例 => class-validator 进行校验。

核心概念总结

  1. IOCAOP、全局模块、动态模块、自定义 provider、生命周期等概念。
  2. middleware 适合通用的处理流程,interceptor 适合处理与具体业务相关的逻辑,例如:从 ExecutionContext 获取目标 classhandler、通过 reflector 获取 metadata 信息、组织响应处理流程。
相关推荐
菜鸟码农_Shi几秒前
Node.js 如何实现 GitHub 登录(OAuth 2.0)
javascript·node.js
AronTing2 分钟前
10-Spring Cloud Alibaba 之 Dubbo 深度剖析与实战
后端·面试·架构
没资格抱怨5 分钟前
如何在vue3项目中使用 AbortController取消axios请求
前端·javascript·vue.js
掘金酱9 分钟前
😊 酱酱宝的推荐:做任务赢积分“拿”华为MatePad Air、雷蛇机械键盘、 热门APP会员卡...
前端·后端·trae
热爱编程的小曾20 分钟前
sqli-labs靶场 less 11
前端·css·less
丁总学Java26 分钟前
wget(World Wide Web Tool) 教程:Mac ARM 架构下安装与使用指南!!!
前端·arm开发·macos
总之就是非常可爱31 分钟前
🚀 使用 ReadableStream 优雅地处理 SSE(Server-Sent Events)
前端·javascript·后端
shoa_top42 分钟前
Cookie、sessionStorage、localStorage、IndexedDB介绍
前端
鸿蒙场景化示例代码技术工程师1 小时前
实现文本场景化鸿蒙示例代码
前端
ᖰ・◡・ᖳ1 小时前
Web APIs阶段
开发语言·前端·javascript·学习