我爲何喜歡NestJS

歡迎關注個人公衆號睿Talk,獲取我最新的文章:
clipboard.png前端

1、前言

作過 Java EE 開發的朋友對 Spring 框架應該很熟悉了,它全面的功能和優秀的設計是得以普遍流行的緣由。它經過靈活使用控制反轉、依賴注入和麪向切面編程等設計理念,極大的規範了大型應用的架構,下降了模塊之間的耦合度,從而提高了應用的開發效率。在 NodeJS 的世界裏,也存在一個全面借鑑 Spring 設計思想的框架,它在 github 上有將近 2w 的 star,npm 的周下載量超過 11w,它就是本文要介紹的 NestJSgit

2、與其它框架的對比

市面上 NodeJS 的服務端框架有不少,如KoaExpressEggJSMidway等,它們功能都很強大,也有很好的生態,插件很是豐富,爲何還須要Nest呢?github

若是是一個簡單的應用,其實用什麼框架都無所謂,一個框架用 100 行代碼實現,另外一個用 80 行,區別不大。但涉及到企業級的應用,分分鐘有上萬行的代碼,代碼的組織結構就變得很重要了。若是代碼拆分不合理,一個 JS 文件就有上千行的代碼,後期的維護成本會很是的高。再考慮到複雜項目參與者衆多,沒有一個規範去約束的話,每一個人寫出來的代碼風格迥異,協做起來會很難受。上文提到的幾個框架對項目代碼的架構要麼是沒約束,要麼就是約束比較弱或者看起來很彆扭。相比之下Nest的實現就很簡潔,用起來很順手。具體細節將在下文進行描述。mongodb

Nest還經過依賴注入的形式實現了控制反轉,只要聲明模塊中的依賴,Nest就會在啓動的時候去建立依賴,而後自動注入到相應的地方。依賴注入最大的做用是代碼解耦,依賴的對象根據不一樣的狀況能夠有多種實現,如單元測試的時候能夠在不改業務代碼的狀況下將依賴的對象換成 Mock 數據。typescript

Nest還踐行了面向切面編程的思想,除了Middleware外,還有Exception FilterPipesGuardsInterceptors幾個預約義的切面,能夠集中進行異常處理、數據驗證、權限驗證和邏輯擴展等功能。Nest自帶如數據驗證等一些經常使用的基於切面的功能,也能夠經過繼承的方式來進行擴展。這些預約義的切面是代碼架構的組成部分,按照這些約定來組織代碼會大大下降往後的維護成本。數據庫

類型系統是後端開發很重要的一環,Nest是使用TypeScript實現的框架,所以原生就支持TypeScript,並且還大量使用了註解,熟悉 Spring 的朋友會感到十分親切。npm

另外,Nest是基於Express實現的,須要的話能夠取到底層的對象,如requestresponse編程

3、實戰

下面的講解將會基於一個簡單的增刪改查 API 服務器,完整項目代碼在這裏,在此就不一步步去介紹編寫過程了。segmentfault

  • 模塊化

Nest是以模塊的形式組織項目的,模塊中能夠聲明ControllerProviderImportExport。打開app.module.ts,內容以下:後端

@Module({
  imports: [CatsModule, MongooseModule.forRoot('mongodb://localhost/nest')],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

能夠看到項目的根模塊AppModule導入了項目中的另外一個模塊CatsModule和外部依賴MongooseModule。另外也聲明瞭模塊內部的ControllerProvider。咱們通常說的ServiceProvider的一種。ModuleControllerProvider的關係見下圖:

項目結構

ControllerProvider都在Module註冊,容器會將Provider注入到Controller中,Module之間能夠相互引用(Import)。像 ES6 的模塊化同樣,Import後只能使用別人Export出來的內容。

  • 註解

再來看一下cats.controller.ts

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get(':name')
  async findOne(@Param('name') name: string): Promise<Cat> {
    return this.catsService.findOne(name);
  }

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

  @Post()
  @HttpCode(201)
  @Header('Cache-Control', 'none')
  async create(
    @Body(new ValidationPipe()) createCatDto: CreateCatDto,
  ): Promise<Cat[]> {
    return this.catsService.create(createCatDto);
  }
}

這文件有大量的註解,這是Nest有別於其它 NodeJS 框架的地方,像極了 Spring。不少註解的含義也與 Spring 的一致,像這裏的@Controller@Get@Post都是用來聲明路由和 http 請求類型的。@Get(':name')是獲取 url 的參數,而@Param('name')是獲取請求體的參數。@Body(new ValidationPipe()) createCatDto: CreateCatDto這行代碼作了不少事,首先將請求體取出,而後校驗數據類型是否合規,而後再將請求體轉換爲 DTO 對象供後續使用。DTO 的定義以下,也是經過註解定義校驗邏輯:

export class CreateCatDto {
  @IsString()
  readonly name: string;
  @IsNumber()
  readonly age: number;
  @IsString()
  readonly breed: string;
}
  • 切面

上面提到的ValidationPipe是內置的Pipe切面,用於校驗參數類型。另外幾種切面和請求處理的順序見下圖:

切面

這裏的Middleware就是Express原生的,其它幾個切面的用法見官方文檔,在此很少做介紹。

  • 鏈接數據庫

例子中使用mongoose鏈接和操做本地MongoDB數據庫。爲了更方便使用,Nest提供了@nestjs/mongoose包,對mongoose包裝了一層,使其更符合Nest的使用風格。操做數據庫的步驟以下:

  • app.module中定義鏈接的數據庫:MongooseModule.forRoot('mongodb://localhost/nest')
  • cat.schema中定義 Schema
  • cats.module中聲明依賴 Model:MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }])
  • cats.service中注入依賴 Model:constructor(@InjectModel('Cat') private readonly catModel: Model<Cat>) {}
  • cats.service中使用 Model:this.catModel.findOne({ name }).exec()

4、總結

本文重點介紹了Nest的設計思想,比較了它跟其它框架的異同,並結合實例詳細講解了具體的用法。文章的寫做目的是爲框架選型者提供一個快速的參考,也爲對Nest感興趣的人提供感性的認識。若是想更詳細的瞭解Nest用法,請看官方文檔

P.S.騰訊雲招前端工程師,歡迎加我微信:

圖片描述