今天的主題不是雀巢咖啡,也不是雀巢奶粉,畢竟我王境澤就是餓死,也不會打你們一點廣告。

前言
近幾年由于 Node.js 的發(fā)展,JavaScript 變成了一種全棧通用語言,同時誕生了諸如 Angular、React、Vue 等一系列提高開發(fā)者生產(chǎn)力的優(yōu)秀前端項目框架,這些框架讓我們開發(fā)更快速、測試更便捷、拓展更簡單。
盡管 Node.js 服務端開發(fā)領域有著諸如 Express,Koa,F(xiàn)astify 等一系列優(yōu)秀的開源庫、工具,但卻缺乏真正意義上的框架。
于是我一直在尋找,油膩的師姐,在哪里……
直到那個冬季,我遇見了她 —— Nest。
Nest 是什么
官方概述是這樣說的。
A progressive Node.js framework for building efficient, reliable and scalable server-side applications, heavily inspired by Angular.
一個深受 Angular 啟發(fā),旨在構建高效、可靠、高拓展性服務端應用程序的先進的 Node.js 框架。
正如 Nest 作者所言——深受 Angular 啟發(fā),所以 Nest 的開發(fā)體驗與 Angular 有些類似。既可以使用 TypeScript(與 Angular 相同,官方推薦),也可以使用 JavaScript 構建項目,同時結合了面向對象編程(Object Oriented Programming)、函數(shù)式編程(Functional Programming)、函數(shù)響應式編程(Functional Reactive Programming)等元素,可兼容主流第三方庫與插件。
目前 Nest 的 Github star 數(shù)在 8000 左右,比我關注那會高了不少。
Nest 特點
從下圖可以看到官方宏觀概述的特點

高拓展性。可正常使用任何第三方庫。
多功能性。適用于多種類型的服務端應用程序編寫。
前沿先進。結合最前沿的 JavaScript 技術特性,將設計模式和成熟方案的理念帶入 Node.js 領域中。
以上是較為宏觀的特性概述,關于其技術上的特點,我認為較為突出的有以下幾點:
官方推薦使用 TypeScript,工程化意義重大。
基于 Express 與 socket.io 套件,兼容其他庫。
語法風格類似 Angular 2+,也類似 Java Spring 框架,大量使用裝飾器(ES7 Decorator)函數(shù),無侵入式的語法使得代碼邏輯更為清晰。
遵循控制反轉(IoC, Inversion of Control)思想,大量使用依賴注入(DI, Dependency Injection)的設計模式,大大降低了單元間的耦合度。
內置的異??刂茖樱‥xception Filter),大至應用,小至邏輯,對各個級別的異常作捕獲與處理,在程序響應上對用戶更為友好。
內置的權限控制層(Guard)與攔截層(Interceptor)。
集成工程化的測試,官方使用 Jest 測試框架。
小試牛刀
我剛開始接觸 Nest 時,新建項目還沒有現(xiàn)在這么方便,相關依賴的安裝都比較人肉。
不過好在前不久正式版 @nestjs/cli 腳手架終于發(fā)布了(猶記得翹首以盼的我),新建項目也變得十分方便。
廢話,少說。腳手架,走一波。(單押 X2)
環(huán)境準備
Visual Studio Code(蘿卜青菜,各有所愛)
Node.js v9.2.0(>= 8.9.0 即可)
@nestjs/cli(官方腳手架)
$ npm i -g @nestjs/cli
· nest-demo(項目初始化)
$ nest new nest-demo
初識 Nest
如無意外,通過腳手架初始化項目的過程大致如下
?? Creating your Nest project...
?? We have to collect additional information:
? description : description
? version : 0.0.0
? author : chenshihao
?? Thank you for your time!
CREATE /nest-demo/.prettierrc (51 bytes)
CREATE /nest-demo/README.md (339 bytes)
CREATE /nest-demo/nodemon.json (147 bytes)
CREATE /nest-demo/package.json (1527 bytes)
CREATE /nest-demo/src/app.controller.spec.ts (588 bytes)
CREATE /nest-demo/src/app.controller.ts (266 bytes)
CREATE /nest-demo/src/app.module.ts (249 bytes)
CREATE /nest-demo/src/app.service.ts (138 bytes)
CREATE /nest-demo/src/main.hmr.ts (329 bytes)
CREATE /nest-demo/src/main.ts (208 bytes)
CREATE /nest-demo/test/app.e2e-spec.ts (593 bytes)
CREATE /nest-demo/test/jest-e2e.json (154 bytes)
CREATE /nest-demo/tsconfig.json (477 bytes)
CREATE /nest-demo/tslint.json (895 bytes)
CREATE /nest-demo/webpack.config.js (695 bytes)
CREATE /nest-demo/.nestcli.json (60 bytes)
? Which package manager would you ?? to use? yarn
????? Take ?? or ?? during the packages installation process and enjoy your time
?? Successfully created project nest-demo
以上操作創(chuàng)建了種子項目并用 yarn 安裝了依賴包。如果沒有安裝成功的話,可手動進行安裝。
我們可以看到,腳手架工具創(chuàng)建了若干文件,包括
-
根目錄
大多為配置文件,如 prettier、nodemon、tslint 等工具的配置文件(若不熟悉可以暫時忽略,將重心放在 *.ts 文件上有助于快速入手框架)
-
test 文件夾
端對端(e2e)測試用例與配置文件
-
src 文件夾
主程序入口文件及若干模塊文件。其中文件層次關系大致如下

我們對文件結構有了初步的認識
入口文件 main.ts 引導程序運行,加載模塊 app.module.ts
模塊中,包括控制器 app.controller.ts,服務提供商 app.service.ts,測試用例 app.controller.spec.ts 等組件。
接下來我們直接以開發(fā)模式運行程序。
$ npm run start:dev
如無意外,我們可以看到控制臺有以下輸出日志。
> nest-demo@0.0.0 start:dev /Users/victor/Desktop/nest-demo
> nodemon
[nodemon] 1.18.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: /Users/victor/Desktop/nest-demo/src/**/*
[nodemon] starting `ts-node -r tsconfig-paths/register src/main.ts`
[Nest] 16999 - 2018-8-20 11:33:49 [NestFactory] Starting Nest application...
[Nest] 16999 - 2018-8-20 11:33:49 [InstanceLoader] AppModule dependencies initialized +8ms
[Nest] 16999 - 2018-8-20 11:33:49 [RoutesResolver] AppController {/}: +14ms
[Nest] 16999 - 2018-8-20 11:33:49 [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 16999 - 2018-8-20 11:33:49 [NestApplication] Nest application successfully started +3ms
從圖中我們能夠得到的信息大致為
運行 nodemon 監(jiān)聽項目文件
通過 ts-node 引導入口文件 main.ts
初始化模塊與路由
在 main.ts 可以看到,默認端口號為 3000
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
我們直接訪問地址 http://localhost:3000 看看效果
$ curl localhost:3000
Hello World!
樣例不作為參考,正式開發(fā)中,推薦使用 Postman 進行 API 調試
Hello World!
看到 “Hello World! ” 的字符串,不禁讓猿熱血澎湃 —— 他做到了!
我們回過頭來看看這個值是怎么來的。
通過結構示例圖,我們知道模塊 (module) 是一個非常重要的概念,而每個模塊的控制器 (controller) 更是扮演著運籌帷幄的角色。
app.controller.ts
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
root(): string {
return this.appService.root();
}
}
app.controller.ts 文件中定義了一個類,通過裝飾器 @Controller() 使其成為一個路由控制類。類方法中,有構造函數(shù) constructor 以及帶有裝飾器 @Get() 的 root 函數(shù)。
app.service.ts
@Injectable()
export class AppService {
root(): string {
return 'Hello World!';
}
}
root 函數(shù)通過調用 service 中的 root 方法,返回字符串 “Hello World!”。
此外,關于 HTTP 響應的操作,Nest 大體上已經(jīng)幫我們安排的明明白白了,這也是官方推薦的寫法。
當我們的函數(shù)返回 JavaScript 對象或數(shù)組時,返回值會被自動轉化成 JSON 對象;
當我們的函數(shù)返回字符串時,返回值不作處理。
響應狀態(tài)碼默認情況下總是 200,除了 POST 請求為 201 外,當然,我們是有辦法通過裝飾器輕松修改返回值的。
當然啦,如果有倔強的老哥非要操作一下 response 對象,也不是不可以滴。
讀到這里,我們便大致了解能用 GET 方法訪問 http://localhost:3000 得到“Hello World!”的來龍去脈了吧。
裝飾器
在上述代碼片段中,我們會發(fā)現(xiàn)許多裝飾器,如 @Controller()、@Injectable() 等。
裝飾器是什么?裝飾器的定義大致如下
An ES2016 decorator is an expression which returns a function and can take a target, name and property descriptor as arguments. You apply it by prefixing the decorator with an @ character and placing this at the very top of what you are trying to decorate. Decorators can be defined for either a class or a property.
ES7 裝飾器是一種返回函數(shù),且可傳遞目標對象、名稱與屬性描述作為參數(shù)的表達式。你可以使用@字符作為前綴并將裝飾器放在你想裝飾的對象上。裝飾器可以被用在一個類或者一個屬性上。
想要了解更多關于裝飾器的知識,可以參考這篇文章或者自行搜索
https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841
裝飾器 (Decorator) 作為 ES7 的一大特性,在 TypeScript 中可以被自由運用,在 Nest 中更是得到了合理的開發(fā)與利用。
縱觀 Nest ,裝飾器貫穿了整個 Nest 框架,如:
模塊,一個帶有 @Module() 裝飾器的類
控制器,一個帶有 @Controller() 裝飾器的類
提供商,一個帶有 @Injectable() 裝飾器的類
通過裝飾器語法裝飾參數(shù),更清晰便捷地取值
為了更好地說明裝飾參數(shù),我們舉個栗子,修改一下 app.controller.ts 和 app.service.ts
app.controller.ts
import { Query } from '@nestjs/common';
@Get()
root(@Query('name') name: string = 'Victor'): string {
return this.appService.root(name);
}
往 app.controller.ts 的 GET 方法中添加參數(shù) name,其中 Query 指的是 url 中的 params 參數(shù)
app.service.ts
@Injectable()
export class AppService {
root(name: string = 'World'): string {
return `Hello ${name}!`;
}
}
配合控制層,往 app.service.ts 添加參數(shù) name 以說明值的改變。
此時服務會自動重啟,我們以 GET 方式請求新地址
http://localhost:3000?name=victor
$ curl http://localhost:3000?name=Victor
Hello Victor!
Surprise!好了,一個 url 傳參的鮮活栗子就這么輕松加愉快的舉完了。
除了 @Query() 之外,Nest 內置的還有一些裝飾器提供給我們直接使用,列舉一下
@Request() —— Express 請求對象
@Response() —— Express 響應對象
@Session() —— Session 對象
@Param(param?: string) —— RESTful 風格的路由參數(shù)
@Body(param?: string) —— 請求體
@Headers(param?: string) —— 請求頭
官方提供的裝飾器固然方便,然鵝在錯綜復雜的需求下,僅僅有這幾個裝飾器還是略顯不足的。在這里,我們甚至可以自定義裝飾器。
這里我們新建一個文件 name.decorator.ts,為了避免邏輯干擾,在這里我們直接返回傳遞的值 data。
name.decorator.ts
import { createParamDecorator } from '@nestjs/common';
export const Name = createParamDecorator((data, req) => {
return data;
});
在 controller 中引用自定義裝飾器,并傳入常量 Victor。
app.controller.ts
import { Name } from 'name.decorator';
@Get()
root(@Name('Victor') name: string): string {
return this.appService.root(name);
}
此時服務重啟,再次訪問 http://localhost:3000。
$ curl localhost:3000
Hello Victor!
通過裝飾器的作用,我們成功的將 name 的值設置成 Victor,這是一個簡單的例子,在實際生產(chǎn)環(huán)境中可以添加更為復雜的邏輯。
依賴注入
如果說裝飾器語法是 Nest 健美的身材,那么依賴注入則可以稱為 Nest 有趣的靈魂了。
簡單來說,依賴注入(DI, Dependency Injection)是一種設計模式,旨在提供對象實例,調用單元在使用依賴對象實例時無需關心其提供方式,而是統(tǒng)一由 DI 系統(tǒng)提供。
想要了解更多依賴注入知識,可以參考以下文章或者自行搜索
https://angular.io/guide/dependency-injection-pattern
事實上,Nest 的設計理念是,“萬物皆可為提供商” —— 如服務類(service)、倉庫類(repository)、工廠類(factory)、幫助類(helper)等等。
我們從實際代碼入手了解這一概念,前面提到的 @Injectable() 裝飾器,其裝飾的對象正是我們所說的“依賴對象”,也就是例子中的 service 類。
app.module.ts
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
在 module 文件中,我們聲明了 AppService 類作為 provider,這是 AppController 可以無實例化直接使用 appService 對象的伏筆。
app.controller.ts
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
root(): string {
return this.appService.root();
}
}
AppService 作為可注入的依賴對象,通過在構造函數(shù) constructor 中聲明的方式,配合 DI 模式,解決了兩者之間的依賴關系與獨立性。于是,我們可以直接在方法中調用 appService。
app.service.ts
@Injectable()
export class AppService {
root(): string {
return 'Hello World!';
}
}
理解了依賴注入的概念,可以更好的閱讀與學習 Nest 的其他內容,如中間件(Middlewire)、管道(Pipe)、防御層(Guard)、攔截器(Interceptor)等等,都是通過依賴注入的方式與我們的控制層進行關聯(lián)的。
實際上,配合 @Inject() 裝飾器,可以實現(xiàn)更為多樣的注入類型,這是更為高級的 provider 注入方式,由于文章篇幅原因,在此就不贅述了。
結束語
這是我的第一篇公眾號文章。
初心是為了介紹 Nest 框架,希望是不僅有落到實處的代碼示例,不至于顯得太空虛;同時也有更為重要的核心概念介紹,讓讀者有更好的大局觀與學習方向。
一篇文章往往不足以描述事物的全部,還有很多優(yōu)秀的思想與巧妙的設計等著我們去探索,我們下篇文章見!
參考資料
原創(chuàng)作者
陳仕豪,年十八,騷話連篇笑哈哈。skr~