Nestjs 是一个用于构建高效、可扩展的 Node.js 服务端应用框架,基于 TypeScript 编写并且结合了 OOP(面向对象的编程)FP(函数式的编程)FRP(函数式响应工式编程) 的相关理念。

下面介绍Nestjs中的一些功能设计,及用法。

# 控制器 Controller

负责处理客户端请求,及相应,用来定义请求路由。

控制器一般不做复杂的业务逻辑,这部分会放到Providers中处理。

常用装饰器:
@Controller('cats') 装饰在类上,接口请求就可以通过/cats访问到
@Get() 装饰在类的方法上,标识请求的类型为Get,也可以传递参数
@Put(':id') 装饰在方法上,接收接口请求PUT /cats/id

# 服务提供者 Providers

Service 一般用来处理业务相关逻辑,如数据库中的数据查询。通过控制器来调用。
装饰器:
@Injectable()

# 模块 Mudule

相关控制器和 Providers 的包装整合。用来管理程序的组织结构。
装饰器:
@Module()
注册对应的控制器和服务,装饰在类上

@Global() // 声明为全局
@Module({
    controllers: [CatsController],
    providers: [CatsService],
    exports: [CatsService] // 导出给其他服务使用
})
export class xxModule {}

# 中间件 Middleware

中间件有两种形式,函数或者类。函数和express中定义一样,这里说明下类的定义方式。
需要使用 @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();
  }
}

在Module中使用中间件,只对当前模块生效

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats'); // 只对cats路由生效
  }
}

如果是全局,可在main.ts中使用 app.use(LoggerMiddleware)

# 异常过滤器 Exception

定义了异常处理层,专门用来负责应用程序中未处理的异常,当控制器抛出错误后,就会被一层过滤器处理。
默认异常过滤器为HttpException

# 自定义异常

通过继承HttpException,实现自己的异常类,并手动抛出该异常:

// 自定义异常类 ForbiddenException
export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

使用

@Get()
async findAll() {
  // 抛出该异常
  throw new ForbiddenException();
}

# 自定义异常过滤器

装饰器:
@Catch(HttpException): 可指定参数,表示捕获指定类型的异常,如果没有参数,捕获所以异常

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

// 捕获HttpException异常
@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): 指定过滤器的类
全局使用:
app.useGlobalFilters(new HttpExceptionFilter())

全局使用时,需要在模块里注册,防止无法进入依赖注入。

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

# 管道 Pipes

主要用途:
1、格式化输入的数据
2、验证输入参数的合法性

# 如何定义管道

使用@Injectable()来装饰类,并且实现PipTransform接口

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;
  }
}

# 使用管道

装饰器:
@UsePipes():装饰在方法上

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

使用在指定参数上:

@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}

# 守卫 Guards

主要用途:接口请求权限,角色校验

# 如何创建守卫

定义类使用@Ingectable()装饰,并实现CanActivate接口

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);
  }
}

# 使用守卫

通过装饰器@UseGuards()来使用

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

在方法上添加元数据,指定那些权限可以访问

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

自定义装饰器角色装饰器

import { SetMetadata } from '@nestjs/common';

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

使用自定义装饰器

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

全局使用
app.useGlobalGuards(new AuthGuard());
并在Module里注册

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

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

# 拦截器 Interceptors

拦截器有很多用处,这些功能设计灵感都来自于面向切面编程(AOP)技术。
主要作用是可以在函数执行前,或执行后绑定一些额外的逻辑,如:
1、执行控制器逻辑前,添加缓存,如果有缓存直接返回
2、对返回的结果进行处理,如固定的返回数据格式:

{
    "code": 0,
    "msg": "",
    "data": {}
}

# 创建拦截器

创建一个类,使用@Injectable()装饰器,并实现NestInterceptor接口。
如下一个例子,在调用函数前后添加日志,记录执行时长

@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)装饰器

@UseInterceptors(LoggingInterceptor)
export class CatsController {}