歡迎關注個人公衆號睿Talk
,獲取我最新的文章:前端
作過 Java EE 開發的朋友對 Spring 框架應該很熟悉了,它全面的功能和優秀的設計是得以普遍流行的緣由。它經過靈活使用控制反轉、依賴注入和麪向切面編程等設計理念,極大的規範了大型應用的架構,下降了模塊之間的耦合度,從而提高了應用的開發效率。在 NodeJS 的世界裏,也存在一個全面借鑑 Spring 設計思想的框架,它在 github 上有將近 2w 的 star,npm 的周下載量超過 11w,它就是本文要介紹的 NestJS。git
市面上 NodeJS 的服務端框架有不少,如Koa
、Express
、EggJS
、Midway
等,它們功能都很強大,也有很好的生態,插件很是豐富,爲何還須要Nest
呢?github
若是是一個簡單的應用,其實用什麼框架都無所謂,一個框架用 100 行代碼實現,另外一個用 80 行,區別不大。但涉及到企業級的應用,分分鐘有上萬行的代碼,代碼的組織結構就變得很重要了。若是代碼拆分不合理,一個 JS 文件就有上千行的代碼,後期的維護成本會很是的高。再考慮到複雜項目參與者衆多,沒有一個規範去約束的話,每一個人寫出來的代碼風格迥異,協做起來會很難受。上文提到的幾個框架對項目代碼的架構要麼是沒約束,要麼就是約束比較弱或者看起來很彆扭。相比之下Nest
的實現就很簡潔,用起來很順手。具體細節將在下文進行描述。mongodb
Nest
還經過依賴注入的形式實現了控制反轉,只要聲明模塊中的依賴,Nest
就會在啓動的時候去建立依賴,而後自動注入到相應的地方。依賴注入最大的做用是代碼解耦,依賴的對象根據不一樣的狀況能夠有多種實現,如單元測試的時候能夠在不改業務代碼的狀況下將依賴的對象換成 Mock 數據。typescript
Nest
還踐行了面向切面編程的思想,除了Middleware
外,還有Exception Filter
、Pipes
、Guards
和Interceptors
幾個預約義的切面,能夠集中進行異常處理、數據驗證、權限驗證和邏輯擴展等功能。Nest
自帶如數據驗證等一些經常使用的基於切面的功能,也能夠經過繼承的方式來進行擴展。這些預約義的切面是代碼架構的組成部分,按照這些約定來組織代碼會大大下降往後的維護成本。數據庫
類型系統是後端開發很重要的一環,Nest
是使用TypeScript
實現的框架,所以原生就支持TypeScript
,並且還大量使用了註解,熟悉 Spring 的朋友會感到十分親切。npm
另外,Nest
是基於Express
實現的,須要的話能夠取到底層的對象,如request
和response
。編程
下面的講解將會基於一個簡單的增刪改查 API 服務器,完整項目代碼在這裏,在此就不一步步去介紹編寫過程了。segmentfault
Nest
是以模塊的形式組織項目的,模塊中能夠聲明Controller
、Provider
、Import
和Export
。打開app.module.ts
,內容以下:後端
@Module({ imports: [CatsModule, MongooseModule.forRoot('mongodb://localhost/nest')], controllers: [AppController], providers: [AppService], }) export class AppModule {}
能夠看到項目的根模塊AppModule
導入了項目中的另外一個模塊CatsModule
和外部依賴MongooseModule
。另外也聲明瞭模塊內部的Controller
和Provider
。咱們通常說的Service
是Provider
的一種。Module
、Controller
和Provider
的關係見下圖:
Controller
和Provider
都在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
中定義 Schemacats.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
用法,請看官方文檔。