Nest.js核心概念淺析

背景介紹

NestNestJS) 是一個用於搭建高性能、可伸縮的Node.js服務端應用的框架。它使用漸進式的JavaScript,內置且徹底支持Typescript(但開發人員能夠使用純JavaScript進行編程) , 同時結合了OOP(面向對象編程),FP(函數式編程)和FRP(響應式編程)的原理。html

Controller

在傳統Node.js服務端應用中,controller用於處理route對應的客戶端請求。每一個controller能夠擁有多個route,不一樣的route會執行不一樣的action(行爲)。在Nest中,咱們能夠使用內置的decorators來對request, response, controllerrouteaction等元素進行定製化的修飾。從而抽離重複代碼,提升開發效率。以下所示:react

// 省略依賴代碼

@Controller('cats') // 模塊名稱,用於定義route前綴:/cats
export class CatsController {
  // 修改該route的成功響應時的狀態碼爲204
  @HttpCode(204)
  // 修改該response的響應頭參數            
  @Header('Cache-Control', 'none')
  // 修飾該route([POST] /cats)的請求方法:POST,@Get、@Post、@Delete同理
  @Post()
  // @Bady 修飾參數,此處將會傳入request.body,@Query()、@Param同理
  create(@Body() createCatDto: CreateCatDto) {
    // 返回響應的數據
    return 'This action adds a new cat';
  }

  @Redirect("http://www.fzzf.top")  // 路由重定向
  @Get(":id")                       // [GET] /cats/:id
  show(@Query("") query: ListAllEntities) {
    // do something
  }
}
複製代碼

對於controller的返回值,Nest支持基於async/await返回的promise,也支持基於RxJS返回的Observable Streams,示例以下:es6

import { of } from 'rxjs';

// controller代碼省略

@Put(":id")
async update(@Param() params: UpdateCatDto): Promise<any> {
    return this.catsService.update(params);
}

// 注意不要加async
@Get()
index(@Query() query: IndexCatDto): Observable<any[]> {
    return of(this.catsService.find(query));
}
複製代碼

Provider

Nest中,provider被定義爲擁有特定功能方法集合的類。經過裝飾器@Injectable()修飾後成爲能夠做爲依賴注入到Nest Module的服務。處於相同的做用域的provider之間只要,其中一個provider也能夠做爲依賴注入到另外一個provider中。以下代碼所示:express

// cats.service
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor(
    private readonly otherService: OtherService
  )

  async create(values: any): Promise<any> {
    return this.otherService.create(values);
  }
}
複製代碼

咱們能夠聲明一個module(參考下一節),將provider注入到controller中。依賴注入的細節由Nest框架內部完成,這裏只介紹基本的依賴注入姿式,以下面代碼所示:編程

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

@Module({
  controllers: [CatsController],  // 當前module的controller 
  providers: [CatsService],       // 須要注入的provider
})
export class CatModule {}

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

@Controller('cats') 
export class CatsController {
  constructor(
    private readonly catsService: CatsService // 依賴注入
  ) { }

  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    // 返回響應的數據
    return this.catsService.create(createCatDto);
  }
}
複製代碼

Module

moduleNest應用的基礎組成單位。一個Nest應用至少擁有一個module(root module)。簡單來講,在Nest中,n個功能module經過依賴注入組成一個業務module,n個業務 module組成一個Nest應用程序。不管是controller仍是provider都須要經過module註冊才能完成依賴的注入。下面的代碼介紹了基礎的module聲明姿式。json

@Module({
  // 依賴的其餘module類
  imports: [],          
  // 業務模塊所需的controller類,它將以爲module如何是實例化爲業務模塊仍是功能模塊
  controller: [],       
  // 提供依賴的類
  provider: [],       
  // 導出模塊中provider,能夠被其餘模塊依賴注入
  exports: [],        
})
export class AppModule {}

複製代碼

這裏咱們能夠經過傳遞exports參數,才完成module之間層級依賴,以及不一樣module中的provider可以被複用。以下面代碼所示:api

// request.module.ts
@Module({
  // 註冊provider 
  provider: [RequestService], 
  // 導出註冊的provider
  exports: [RequestService],        
})
export class RequestModule {}

// api.module.ts
@Module({
  // 導入一個RequestModule,Nest會註冊其中export的provider
  imports: [RequestModule],     
  // 註冊controller
  controllers: [ApiController], 
})
export class ApiModule {}

// api.controller.ts
@Controller('api') 
export class ApiController {
  constructor(
    private readonly requestService: RequestService  // 依賴注入
  ) { }
}
複製代碼

除此以外,咱們還能夠經過裝飾器@Globalmodule A修飾爲一個全局模塊,這樣任意module不須要import就能夠注入module Aexport的依賴。同時咱們也能夠經過特定參數實現module動態導入promise

Middleware

Nest是一個上層框架,底層依賴於express框架(或hapi),因此Nestmiddleware等同於express中的middleware,咱們能夠使用它來訪問requestresponse,以及對處理程序作一些前置或後置操做。它的聲明方式以下:bash

  • 聲明
// 方式1
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class CatchMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    console.log('catch...');
    next();
  }
}

// 方式2
export function catchError(req, res, next) {
  console.log(`catch...`);
  next();
};
複製代碼
  • 使用
// 方式1
@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(CatchMiddleware)
      .forRoutes(CatsController);
  }
}

// 方式2
const app = await NestFactory.create(AppModule);
app.use(catchError);
await app.listen(3000);
複製代碼

Exception filters

Nest內置了處理程序未處理的異常捕獲層(exception-filter)以及友好的異常處理對象。咱們經過聲明exception來對catch全局或局部的異常拋出,如全局的error。以下面代碼所示:app

http-exception.filter.ts

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

// @Catch()參數爲空時,catch全部類型的異常
@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,
      });
  }
}

// 使用
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
複製代碼

同時咱們還能夠繼承Nest內置的exception對象來規範化系統異常的格式,以下代碼所示:

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}
// 錯誤打印
{
  "status": 403,
  "error": "This is a custom message"
}
複製代碼

Pipes

PipeNest中用於對controller中處理程序的request作前置處理。

  • 格式化請求輸入,如req.query、req.param、req.body等;
  • 校驗請求輸入,若正確則按原樣傳遞,反正則拋出異常exception; 這裏聲明方式能夠參考官方文檔

Guards

Nest中,Guard被設計擁有單一指責的前置處理程序,用於通知route對應處理函數是否須要執行。官方文檔中推薦使用Guard來實現 authorization相關的功能,如權限校驗、角色校驗等,來替代傳統express應用程序中使用middleware實現的思路。

官方認爲middleware本質上是很「愚蠢」的,由於它沒法知道在調用next以後將執行哪個處理函數。而Guard能夠經過ExecutionContext獲取當前request的執行上下文,清楚的知道哪個handler將要執行,所以能夠保證開發者在正確的requestresponse週期添加處理邏輯。

它的聲明方式以下:

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);
  }
}
複製代碼

同時guard也支持全局、單個controller或單個handle method進行前置處理。詳情能夠參考官方文檔

Interceptors

Nest中,Interceptor的設計靈感來自於[AOP](https://en.wikipedia.org/wiki/Aspect-oriented_programming),用於給某個處理函數模塊(如controller)添加前置/後置操做。實現功能如:輸入/輸出的額外操做、exception的過濾、在特定條件下重載當前處理函數的邏輯等。它的聲明方式以下:

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class HttpInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
   // context當前執行上下文
   // next下一個將要執行的處理函數
    return next.handle().pipe(
      map((value: any) => ({
        code: 0,
        data: value,
      }))
    );
  }
}

// 使用
UseInterceptors(HttpInterceptor)
export class CatsController {}
複製代碼

Request Lifecycle

以上介紹的各類Nest組件被註冊以後由Nest框架管理,它們在一個請求的某個聲明週期階段被執行。具體執行順序以下:

  1. 請求輸入
  2. 全局middleware
  3. 根模塊(root modulemiddleware
  4. 全局Guards
  5. Controller Guards
  6. route對應處理函數上使用@UseGuards()註冊的Guard
  7. 全局interceptors(controller前置處理)
  8. controllerinterceptors(controller前置處理)
  9. route對應處理函數上使用@UseInterceptors()註冊的interceptors(controller前置處理)
  10. 全局pipes
  11. controllerpipes
  12. route對應處理函數上使用@UsePipes()註冊的Pipes
  13. route對應處理函數參數註冊的Pipes (如:@Body(new ValidationPipe())
  14. route對應處理函數
  15. route對應處理函數依賴的Serivce
  16. route對應處理函數上使用@UseInterceptors()註冊的interceptors(controller後置處理)
  17. controllerinterceptors(controller後置處理)
  18. 全局interceptors(controller後置處理)
  19. route對應處理函數註冊的Exception filters
  20. controller註冊的Exception filters
  21. 全局註冊的Exception filters
  22. 響應輸出

參考資料

相關文章
相關標籤/搜索