首页>>前端>>JavaScript->快速入门nest.js(8/10)

快速入门nest.js(8/10)

时间:2023-12-01 本站 点击:0

基本

NestJS中,我们还有4个额外的功能构建块。

嵌套构建块可以是:

全局范围

控制器范围

方法范围

参数范围<仅适用于管道>

这些不同的绑定拘束为您提供了应用程序中不同级别的力度和控制,每个都不会覆盖另外一个,而是分层在顶部。

进入main.ts我们看到之前就是用过全局的管道:

import { ValidationPipe } from '@nestjs/common';import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';async function bootstrap() {  const app = await NestFactory.create(AppModule);  app.useGlobalPipes(new ValidationPipe({    whitelist: true,    transform: true,    forbidNonWhitelisted: true,    transformOptions: {      enableImplicitConversion: true,    },  }));  await app.listen(3000);}bootstrap();

自行设置和实例化它的一大限制是:我们不能在这里注入任何的依赖,因为我们将它设置在任何NestJS模块上下文之外,那我们该如何解决这个问题呢?

我们可以选择使用基于自定义提供程序的语法直接从Nest模块内部设置管道

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],

APP_MODULE是由@nestjs/core中导出的特殊令牌,以这种方式提供ValidationPipe,可以让我们在AppModule的范围内实例化ValidationPipe并在创建后将其注册为全局管道<其他构建模块功能也有相同的标记>。

假设我们想将ValidationPipe绑定到仅在CoffeesController中定义的每个路由处理程序

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...

你也可以传递一个实例:

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...

从而在实现this确切场景时,非常有用。当然,最佳实践为使用类而不是实例,这减少了内存使用,因为Nest可以在整个模块中轻松重用同一类的实例

方法范围:

  @UsePipes(ValidationPipe)  @Get(':id')  findOne(@Param('id') id: string) {    // 选择传入某个字符串    return this.coffeeService.findOne(id); // 使用service中的方法替换之前写的空方法  }

仅适用于pipe的参数范围:

  @Patch(':id')  update(@Param('id') id: string, @Body(ValidationPipe) updateCoffeeDto: UpdateCoffeeDto) {    return this.coffeeService.update(id, updateCoffeeDto);  }

捕捉异常ExceptionFilter

// nest g filter common/filters/http-exception

import {  ArgumentsHost,  Catch,  ExceptionFilter,  HttpException,} from '@nestjs/common';import { Response } from 'express';@Catch(HttpException) // 处理的是HttpExceptionexport class HttpExceptionFilter<T extends HttpException>  implements ExceptionFilter{  catch(exception: T, host: ArgumentsHost) {    const ctx = host.switchToHttp(); // 这个switchToHttp可以使我们能够访问本机飞行请求或响应对象    const response = ctx.getResponse<Response>(); // 此方法返回我们的底层平台响应,默认情况下是Express    const status = exception.getStatus();    const exceptionResponse = exception.getResponse();  // 获取原始异常响应    const error =      typeof response === 'string'  // 为了错误统一返回object        ? { message: exceptionResponse }        : (exceptionResponse as object);    response.status(status).json({      ...error    });  // 发回响应设置statusCode  }}

到目前为止,这里的HttpExceptionFilter还没有任何真正做任何独特的事情。

比如现在我们可以增加这个信息;

    response.status(status).json({      ...error,      timestamp: new Date().toISOString()   // 增加的    });  // 发回响应设置statusCode

由于我们不需要任何外部提供程序,因此我们可以使用main.ts文件中的app实例全局绑定这个ExceptionFilter

async function bootstrap() {  const app = await NestFactory.create(AppModule);// ...  app.useGlobalFilters(new HttpExceptionFilter)  await app.listen(3000);}

然后现在我们测试它:

路由守卫

可以用来检验token是否有效,从而进行下一步的请求

首先创建一个负责两件事的Guard

验证API_KEY是否存在于授权标头中;

其次确定是否将正在访问的路由指定为公共的(私有的必须有API_KEY才能访问);

首先:

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],0

common这个文件夹我们可以在其中保存任何与特定于无关的东西。

守卫的一个重要要求就是要实现从@nest/common导出的canActive接口

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],1

这个类返回的bool值指定当前请求是被允许继续还是拒绝访问。

然后在main.ts中添加我们新的ApiGuardappUseGlobalCuards()

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],2

为了保证api_key不被推送,我们将api_key定义为环境变量。

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],3

然后在守卫这里,我们希望任何未标记为公共的请求需要验证API_KEY

这里假设调用者将此密钥作为authorization header传递;

获取HTTP请求相关的信息,我们需要从继承自ArgumentsHostExecutionContext访问它;

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],4

然后测试它:

这里我们需要实现上一节提到的检测当前路由是否被声明为公共的。

@SetMetadata

那么我们该以哪种方式指定应用程序中的哪些端点是公共的呢?或者想要任何数据与控制器或路由一起存储?

这就是自定义元数据发挥作用的地方:@SetMetadata

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],5

封装装饰器

上述做法并不是最佳实践,我们可以自定义装饰器@public来实现同样的功能。

首先,在/common/下创建一个名为decorators的文件夹用来存储我们可能制作的任何其他未来的装饰器,然后创建public.decorator,这个文件我们要导出两个东西:

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],6

然后换掉:

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],7

Reflector类

为了在路由守卫中访问我们的路由元数据,我们需要使用Reflector类,它允许我们在特定上下文检索元数据。

首先在constructor中注入该类:

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],8

这时候直接运行会出现如下错误:

这是因为依赖于其他类的全局守卫必须在@Module上下文中注册(这样才能被实例化),我们可以直接在/common/文件夹中创建一个module文件nest g mo common

// app.moduleproviders: [AppService,{provide: APP_PIPE, useClass: ValidationPipe}],9

这里局部配置了,所以我们需要在main.ts中删除useGlobalGuard;

测试发现不设置任何的headers也能请求成功:

而请求没有@Public的路由并不设置Headers会请求失败。

拦截器

拦截器通过向现有代码添加额外的行为而无需修改代码本身,它可以使我们:

在方法执行之前或之后绑定额外的逻辑;

转换从方法返回的结果;

转换方法抛出的异常;

扩展基本方法行为;

甚至覆盖一个方法-取决于特定条件

例如做一些像缓存各种响应这样的事情;

这里创建一个例子,希望我们所有的响应都有data属性。这里创建的拦截器将拦截处理所有传入的请求,并自动为我们包装我们的数据:

初始

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...0

同样,这里需要实现一个NestInterceptor接口:

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...1

log例子

<注意如何在之后自定义逻辑的>:

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...2

同样我们还是需要将其导入才能使用(这里是全局导入):

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...3

然后测试POST一个请求创建coffee之后:

数据包装器

现在我们实现最开始提到的数据包装器:

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...4

测试(被包裹在data属性下面了):

处理超时

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...5

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...6

同样将其绑定到全局:

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...7

测试(这里在findall里面设置一个很长的setimeout来模拟)

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...8

然而这个message并不是特别的友好,我们应该如何修改它呢?

@UsePipes(ValidationPipe)@Controller('coffees')export class CoffeesController {    // ...9

创建常规管道

先前

管道通常的两个用例:

转换

验证

nest在方法被调用前除法一个管道,管道也会接收要传递给方法的参数,nest提供了几个开箱即用的管道:

ValidationPipe

ParseArrayPipe:解析和验证数组;

构建自己的Pipes

创建一个管道,它会自动将任何传入的字符串解析为整数ParseIntPipe(当然nest已经有现成的pipe可以使用,这里为了学习而重新实现)

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...0

和之前的差不多,这里需要实现的是PipeTransform接口

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...1

添加转换的逻辑:

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...2

现在,我们就可以将我们的管道绑定到@Param()上了;

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...3

中间件

中间件是一个在处理路由处理程序和其他构建块之前调用的函数,这包括了拦截器、守卫和管道。中间件可以访问请求和响应对象,并且不专门绑定到任何方法,而是绑定到指定的路由路径。中间件函数可以执行以下任务:

执行代码

更改请求和响应对象

结束请求响应周期

甚至在调用堆栈中调用next()中间件函数

使用中间件是时,如果当前中间件函数没有结束请求/响应周期,它就必须调用next()方法,该方法将控制权传递给下一个中间件函数,否则,请求将被挂起永远不会完成。

中间件可以是函数和类:

函数中间件是无状态的,它不能被注入依赖项,并且无权访问nest容器;

类中间件可以依赖外部依赖并注入在同一模块范围内的提供程序;

创建:

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...4

同样这里需要实现NestMiddleware接口:

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...5

在这之前先要在common.module中去注册它,在这里,我们首先要确保CommonModule继承于NestModule接口,这个接口需要我们提供configure()方法它以MiddlewareConsumer作为参数。MiddlewareConsumer提供了一组有用的方法来将中间件绑定到特定的路由。

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...6

实现一个记录往返时间的中间件:

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...7

自定义装饰器

这里创建一个获取协议的参数装饰器(效果和@Body获取request.body差不多):

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...8

使用:

@UsePipes(new ValidationPipe())@Controller('coffees')export class CoffeesController {    // ...9

\

原文:https://juejin.cn/post/7097941883778777125


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/6412.html