Nestjs基础概要

759 阅读4分钟

Nest 基础概要

update 2023.05.09

入口 main.ts

async function bootstrap() {
	const app = await NestFactory.create(AppModule)
  
  // 可通过 app.use 添加全局配置
  app.use(logger) // 全局 logger 中间件
  app.useGlobalFilters(new HttpExceptionFilter()) // 全局异常过滤器
  app.useGlobalPipes(new ValidationPipe()) // 全局管道处理
  app.useGlobalGuards(new RolesGuards()) // 全局守卫
  app.useGlobalInterceptors(new LoggingInterceptor()) // 全局拦截器
  
  await app.listen(3000)
}

模块 Module

  • 每个应用至少有一个模块,作为应用的入口
  • 使用 @Module 装饰
  • nest g module cats
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 {}

请求链路流程

  • 客户端 发起请求开始,一系列的处理流程顺序
  • Client Side -> Middleware -> Guards -> Interceptors -> pipes -> [Controllers -> Providers]: 具体的执行函数
  • Exception Filter : 中间件、守卫、拦截器、管道以及函数执行时抛出的异常,执行顺序均会从抛出错误的那一刻转至异常拦截器

1. 中间件 Middleware

  • 函数,可以访问到请求响应对象
  • 通过 next() 将控制权传递给下一个中间件函数
  • Nest 可以通过函数或者类实现中间件
// 类的方式, 使用 @Injectable 装饰, 并实现 NestMiddleware 接口
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response } from 'express'

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    console.log('Request...')
    next()
  }
}
// 函数的方式
export function logger(req, res, next) {
  console.log(`Request...`)
  next()
}

2. 守卫 Guards

  • 中间件逻辑之 后、拦截器/管道之 前 执行
  • 决定请求是否要被控制器处理,一般使用在权限、角色场景中
  • 相比于中间件(调用 next() 任务完成),守卫还需要关注上下游
// 使用 @Injectable 装饰, 实现 CanActivate 接口
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(RolesGuard) 使用
  • 此时已经有用了守卫,但没有执行上下文
  • 应该有一些需要访问到的权限类型
  • 通常使用 @SetMetadata() 对控制器添加元数据
@Post()
@SetMetadata('role', ['admin'])
async creat() { ... }
// 实现一个自定义装饰器
import { SetMetadata } from '@nestjs/common'

export const Roles = (...roles: string[]) => SetMetadata('roles', roles)

// 使用
@Post()
@Roles('admin')
async create() { ... }

3. 拦截器 Interceptors

  • 使用 @Injectable 装饰,必须实现 NestInterceptor 接口
  • 在函数执行前后绑定额外的逻辑
  • 转换一个函数返回值
  • 转换函数抛出的异常
  • 扩展基础函数行为
  • 根据特定条件完全重写一个函数
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } 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(() => console.log(`After... ${Date.now() - now}ms`)),
      )
  }
}

// 通过 @UseInterceptors(LoggingInterceptor) 使用
// 将所有响应中出现的 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 ))
  }
}

4. 管道 Pipes

  • 控制器接收请求 前 处理,偏向于服务端
  • interceptor start -> pipe validate -> handler -> interceptor end
  • 使用 @Injectable 装饰,必须实现 PipeTransform 接口
  • 转换输入数据为目标格式 && 验证输入数据
  • 管道会在异常范围内执行 - 异常处理层可以处理管道异常
  • 内置 ValidationPipeParseIntPipe
// 配合第三方库使用
// yarn add class-validator class-transformer
import { validate } from 'class-validator'
import { plainToClass } from 'class-transformer'

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value)
    const errors = await validate(object)
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed')
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object]
    return !types.includes(metatype)
  }
}

// 通过 @UsePipes(ValidationPipe) 使用

5. 控制器 Controller

  • 处理客户端请求并发送响应内容
  • 路由决定具体处理的请求(调用 Service 来处理)
  • @Controller() && @Get()、@Post()...
  • nest g controller cats
import { Controller, Get } from '@nestjs/common'

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

6. 提供者 Providers

  • 控制反转 IOC 模式中的依赖注入特性
  • 使用 @Injectable() 装饰
  • 通常只关注于处理业务逻辑,供 Controller 调用
  • nest g service cats
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

// ./interfaces/cat.interface
export interface Cat {
	name: string
  age: number
  breed: string
}
// 将Service注入到控制器中
import { CatsService } from './cats.service'
@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

异常过滤器 Exception Filter

  • 默认情况下会被 全局 异常过滤器 HttpException 或它的子类处理
  • 若未识别,会返回给客户端 500, Internet server error 的报错
// @Catch 装饰, 并实现 ExceptionFilter 接口
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'
import { Request, 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 request = ctx.getRequest<Request>()
    const status = exception.getStatus()

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      })
  }
}

// 通过 @UseFilters(HttpExceptionFilter) 使用
// 方法作用域、控制器作用域、全局作用域...

参考资料