最近已經使用過一段時間的nestjs,讓人寫着有一種java spring的感受,nestjs可使用express的全部中間件,此外完美的支持typescript,與數據庫關係映射typeorm配合使用能夠快速的編寫一個接口網關。本文會介紹一下做爲一款企業級的node框架的特色和優勢。java
- 從依賴注入(DI)談起
- 裝飾器和註解
- nestjs的「洋蔥模型」
- nestjs的特色總結
原文在個人博客中: https://github.com/forthealll...node
歡迎star和forkgit
從angular1.x開始,實現了依賴注入或者說控制反轉的模式,angular1.x中就有controller(控制器)、service(服務),模塊(module)。筆者在早年間寫過一段時間的angular1.3,下面舉例來講明:github
var myapp=angular.module('myapp',['ui.router']); myapp.controller('test1',function($scope,$timeout){} myapp.controller('test2',function($scope,$state){}
上面這個就是angular1.3中的一個依賴注入的例子,首先定義了模塊名爲「myapp」的module, 接着在myapp這個模塊中定義controller控制器。將myapp模塊的控制權交給了myapp.controller函數。具體的依賴注入的流程圖以下所示:spring
myapp這個模塊如何定義,因爲它的兩個控制器決定,此外在控制器中又依賴於$scope、$timeout等服務。這樣就實現了依賴注入,或者說控制反轉。typescript
用一個例子來通俗的講講什麼是依賴注入。數據庫
class Cat(){ } class Tiger(){ } class Zoo(){ constructor(){ this.tiger = new Tiger(); this.cat = new Cat(); } }
上述的例子中,咱們定義Zoo,在其constructor的方法中進行對於Cat和Tiger的實例化,此時若是咱們要爲Zoo增長一個實例變量,好比去修改Zoo類自己,好比咱們如今想爲Zoo類增長一個Fish類的實例變量:express
class Fish(){} class Zoo(){ constructor(){ this.tiger = new Tiger(); this.cat = new Cat(); this.fish = new Fish(); } }
此外若是咱們要修改在Zoo中實例化時,傳入Tiger和Cat類的變量,也必須在Zoo類上修改。這種反反覆覆的修改會使得Zoo類並無通用性,使得Zoo類的功能須要反覆測試。json
咱們設想將實例化的過程以參數的形式傳遞給Zoo類:bootstrap
class Zoo(){ constructor(options){ this.options = options; } } var zoo = new Zoo({ tiger: new Tiger(), cat: new Cat(), fish: new Fish() })
咱們將實力化的過程放入參數中,傳入給Zoo的構造函數,這樣咱們就不用在Zoo類中反覆的去修改代碼。這是一個簡單的介紹依賴注入的例子,更爲徹底使用依賴注入的能夠爲Zoo類增長靜態方法和靜態屬性:
class Zoo(){ static animals = []; constructor(options){ this.options = options; this.init(); } init(){ let _this = this; animals.forEach(function(item){ item.call(_this,options); }) } static use(module){ animals.push([...module]) } } Zoo.use[Cat,Tiger,Fish]; var zoo = new Zoo(options);
上述咱們用Zoo的靜態方法use往Zoo類中注入Cat、Tiger、Fish模塊,將Zoo的具體實現移交給了Cat和Tiger和Fish模塊,以及構造函數中傳入的options參數。
在nestjs中也參考了angular中的依賴注入的思想,也是用module、controller和service。
@Module({ imports:[otherModule], providers:[SaveService], controllers:[SaveController,SaveExtroController] }) export class SaveModule {}
上面就是nestjs中如何定一個module,在imports屬性中能夠注入其餘模塊,在prividers注入相應的在控制器中須要用到的service,在控制器中注入須要的controller。
在nestjs中,完美的擁抱了typescript,特別是大量的使用裝飾器和註解,對於裝飾器和註解的理解能夠參考個人這篇文章:Typescript中的裝飾器和註解。咱們來看使用了裝飾器和註解後,在nestjs中編寫業務代碼有多麼的簡潔:
import { Controller, Get, Req, Res } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll(@Req() req,@Res() res) { return 'This action returns all cats'; } }
上述定義兩個一個處理url爲「/cats」的控制器,對於這個路由的get方法,定義了findAll函數。當以get方法,請求/cats的時候,就會主動的觸發findAll函數。
此外在findAll函數中,經過req和res參數,在主題內也能夠直接使用請求request以及對於請求的響應response。好比咱們經過req上來獲取請求的參數,以及經過res.send來返回請求結果。
這裏簡單講講在nestjs中是如何分層的,也就是說請求到達服務端後如何層層處理,直到響應請求並將結果返回客戶端。
在nestjs中在service的基礎上,按處理的層次補充了中間件(middleware)、異常處理(Exception filters)、管道(Pipes),守衛(Guards),以及攔截器(interceptors)在請求到打真正的處理函數之間進行了層層的處理。
上圖中的邏輯就是分層處理的過程,通過分層的處理請求才能到達服務端處理函數,下面咱們來介紹nestjs中的層層模型的具體做用。
在nestjs中的middle徹底跟express的中間件一摸同樣。不只如此,咱們還能夠直接使用express中的中間件,好比在個人應用中須要處理core跨域:
import * as cors from 'cors'; async function bootstrap() { onst app = await NestFactory.create(/* 建立app的業務邏輯*/) app.use(cors({ origin:'http://localhost:8080', credentials:true })); await app.listen(3000) } bootstrap();
在上述的代碼中咱們能夠直接經過app.use來使用core這個express中的中間件。從而使得server端支持core跨域等。
初此以外,跟nestjs的中間件也徹底保留了express中的中間件的特色:
在nestjs中,中間件跟express中徹底同樣,除了能夠複用express中間件外,在nestjs中針對某一個特定的路由來使用中間件也十分的方便:
class ApplicationModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); } }
上面就是對於特定的路由url爲/cats的時候,使用LoggerMiddleware中間件。
Exception filters異常過濾器能夠捕獲在後端接受處理任何階段所跑出的異常,捕獲到異常後,而後返回處理過的異常結果給客戶端(好比返回錯誤碼,錯誤提示信息等等)。
咱們能夠自定義一個異常過濾器,而且在這個異常過濾器中能夠指定須要捕獲哪些異常,而且對於這些異常應該返回什麼結果等,舉例一個自定義過濾器用於捕獲HttpException異常的例子。
@Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception.getStatus(); response .status(status) .json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, }); } }
咱們能夠看到host是實現了ArgumentsHost接口的,在host中能夠獲取運行環境中的信息,若是在http請求中那麼能夠獲取request和response,若是在socket中也能夠獲取client和data信息。
一樣的,對於異常過濾器,咱們能夠指定在某一個模塊中使用,或者指定其在全局使用等。
Pipes通常用戶驗證請求中參數是否符合要求,起到一個校驗參數的功能。
好比咱們對於一個請求中的某些參數,須要校驗或者轉化參數的類型:
@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; } }
上述的ParseIntPipe就能夠把參數轉化成十進制的整型數字。咱們能夠這樣使用:
@Get(':id') async findOne(@Param('id', new ParseIntPipe()) id) { return await this.catsService.findOne(id); }
對於get請求中的參數id,調用new ParseIntPipe方法來將id參數轉化成十進制的整數。
Guards守衛,其做用就是決定一個請求是否應該被處理函數接受並處理,固然咱們也能夠在middleware中間件中來作請求的接受與否的處理,與middleware相比,Guards能夠得到更加詳細的關於請求的執行上下文信息。
一般Guards守衛層,位於middleware以後,請求正式被處理函數處理以前。
下面是一個Guards的例子:
@Injectable() export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); return validateRequest(request); } }
這裏的context實現了一個ExecutionContext接口,該接口中具備豐富的執行上下文信息。
export interface ArgumentsHost { getArgs<T extends Array<any> = any[]>(): T; getArgByIndex<T = any>(index: number): T; switchToRpc(): RpcArgumentsHost; switchToHttp(): HttpArgumentsHost; switchToWs(): WsArgumentsHost; } export interface ExecutionContext extends ArgumentsHost { getClass<T = any>(): Type<T>; getHandler(): Function; }
除了ArgumentsHost中的信息外,ExecutionContext還包含了getClass用戶獲取對於某一個路由處理的,控制器。而getClass用於獲取返回對於指定路由後臺處理時的處理函數。
對於Guards處理函數,若是返回true,那麼請求會被正常的處理,若是返回false那麼請求會拋出異常。
攔截器能夠給每個須要執行的函數綁定,攔截器將在該函數執行前或者執行後運行。能夠轉換函數執行後返回的結果等。
歸納來講:
interceptors攔截器在函數執行前或者執行後能夠運行,若是在執行後運行,能夠攔截函數執行的返回結果,修改參數等。
再來舉一個超時處理的例子:
@Injectable() export class TimeoutInterceptor implements NestInterceptor{ intercept( context:ExecutionContext, call$:Observable<any> ):Observable<any>{ return call$.pipe(timeout(5000)); } }
該攔截器能夠定義在控制器上,能夠處理超時請求。
最後總結一下nestjs的優缺。
nestjs的優勢: