NestJS入門

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

一、前言

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

二、與其它框架的對比

市面上 NodeJS 的服務端框架有很多,如KoaExpressEggJSMidway等,它們功能都很強大,也有很好的生態,插件非常豐富,爲什麼還需要Nest呢?

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

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

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

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

另外,Nest是基於Express實現的,需要的話可以取到底層的對象,如requestresponse

三、實戰

下面的講解將會基於一個簡單的增刪改查 API 服務器,完整項目在這裏

  • 模塊化

項目是以模塊的形式組織起來的,模塊中可以聲明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()

四、總結

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章