ZHE LIN

一切伟大的思想和行动都有一个微不足道的开始

Nest.js学习笔记

控制器Controller

@Controller() 装饰

使用

import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

Providers/Services

@Injectable() 装饰

使用

import { CatsService } from './cats.service';

// 构造函数参数可以使用 @Inject(CatsService) 装饰器

// 控制器
@Controller('cat')
export class CatController {
    constructor( private readonly catService: CatsService ) { }

    @Post()
    async createCat(@Body() cat: Cat): Promise<Result> {
        await this.catService.createCat(cat);
        return { code: 200, message: '创建成功' };
    }

    @Delete(':id')
    async deleteCat(@Param('id') id: number): Promise<Result> {
        await this.catService.deleteCat(id);
        return { code: 200, message: '删除成功' };
    }

    @Put(':id')
    async updateCat(@Param('id') id: number, @Body() cat: Cat): Promise<Result> {
        await this.catService.updateCat(id, cat);
        return { code: 200, message: '更新成功' };
    }

    @Get(':id')
    async findOneCat(@Param('id') id: number): Promise<Result> {
        const data = await this.catService.findOneCat(id);
        return { code: 200, message: '查询成功', data };
    }
}

注册 Provider

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class ApplicationModule {}

模块

@Module() 装饰

有以下取值:

  • providers: 可以被 Nest 的注入器初始化的 providers,至少会在此模块中共享
  • controllers:这个模块需要用到的控制器集合
  • imports:引入的其它模块集合
  • exports:此模块提供的 providers 的子集,其它模块引入此模块时可用

中间件

@Injectable() 装饰

中间件就是一个函数,在路由处理器之前调用。这就表示中间件函数可以访问到请求和响应对象以及应用的请求响应周期中的 next() 中间间函数。

中间件函数主要做以下的事情:

  • 执行任意的代码
  • 对请求/响应做操作
  • 终结请求-响应周期
  • 调用下一个栈中的中间件函数
  • 如果当前的中间间函数没有终结请求响应周期,那么它必须调用 next() 方法将控制权传递给下一个中间件函数。否则请求将被挂起

使用

1、应用中间件

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

2、多个中间件

consumer.apply(cors(), helmet(), logger)
    .forRoutes(CatsController);

3、全局中间件

const app = await NestFactory.create(ApplicationModule);
app.use(logger);
await app.listen(3000);

异常过滤器

使用

  • 控制器:@UseFilters(HttpExceptionFilter)装饰
  • 全局:app.useGlobalFilters(new HttpExceptionFilter());
  • 模块:
@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})

管道

@Injectable() 装饰,它必须实现 PipeTransform 接口implements PipeTransform

过滤器在客户端发送请求处理,管道则在控制器接收请求处理。

  • 转换/变形:转换输入数据为目标格式
  • 验证:对输入数据时行验证,如果合法让数据通过管道,否则抛出异常。

管道会在异常范围内执行,这表示异常处理层可以处理管道异常。如果管道发生了异常,控制器的执行将会停止

内置管道

Nest 内置了两种管道:ValidationPipe 和 ParseIntPipe。

我们可以创建.dto.ts来校验参数:

export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

但是 TypeScript 类型是静态的、编译时类型,当编译成 JavaScript 后在运行时并没有任何类型校验。这时我们就需要自己去验证,或者借助第三方工具、库来验证。类验证器:class-validator

ValidationPipe使用

1、参数作用域

@Post()
async create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

2、方法作用域

@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

管道修饰器入参可以是类而不必是管道实例:

@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

3、全局作用域

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

ParseIntPipe使用

比如说上面的例子中 age 虽然声明的是 int 类型,但是我们知道 HTTP 请求传递的都是纯字符流,所以通常我们还要把期望传进行类型转换。

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 await this.catsService.findOne(id);
}

守卫/Guards

@Injectable() 装饰的类,它必须实现 CanActivate 接口implements CanActivate

守卫只有一个职责,就是决定请求是否需要被控制器处理。一般用在权限、角色的场景中。

守卫和中间件的区别在于:中间件很简单,next 方法调用后中间的任务就完成了。但是守卫需要关心上下游,它需要鉴别请求与控制器之间的关系。

守卫会在中间件逻辑之、拦截器/管道之执行。

授权守卫

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

canActivate 返回 true,控制器正常执行,false 请求会被 deny。

使用

1、控制器

@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}

2、全局

const app = await NestFactory.create(ApplicationModule);
app.useGlobalGuards(new RolesGuard());

由于我们在根模块外层引用了全局守卫,这时守卫无法注入依赖。所以我们还需要在要模块上引入。

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class ApplicationModule {}

更深层次的权限校验可能需要@SetMetadata() 装饰器。

拦截器/Interceptors

使用 @Injectable() 装饰的类,它必须实现 NestInterceptor 接口implements NestInterceptor

拦截器有一系列的功能,这些功能的设计灵感都来自于面向切面的编程(AOP)技术。这使得下面这些功能成为可能:

  • 在函数执行前/后绑定额外的逻辑
  • 转换一个函数的返回值
  • 转换函数抛出的异常
  • 扩展基础函数的行为
  • 根据特定的条件完全的重写一个函数(比如:缓存)

每个拦截器都要实现 intercept()方法,此方法有两个参数:

执行上下文:第一个是 ExecutionContext 实例(这和守卫中的对象一样)。ExecutionContext 继承自 ArgumentsHost。在@nestjs/common库中。

export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;   // 返回控制器类的类型,非实例。
  getHandler(): Function;   // 返回一个将会被调用的路由处理器的引用。
}

调用处理器:第二个参数是一个 CallHandler。CallHandler 接口实现了handle()方法,这个方法就是你可以在你拦截器的某个地方调用的路由处理器。如果你的intercept()方法中没调用handle()方法,那么路由处理器将不会被执行。在@nestjs/common库中。

使用

1、控制器使用

@UseInterceptors(LoggingInterceptor)
export class CatsController {}