今天的主題不是雀巢咖啡,也不是雀巢奶粉,畢竟我王境澤就是餓死,也不會打你們一點(diǎn)廣告。
前言
近幾年由于 Node.js 的發(fā)展,JavaScript 變成了一種全棧通用語言,同時誕生了諸如 Angular、React、Vue 等一系列提高開發(fā)者生產(chǎn)力的優(yōu)秀前端項(xiàng)目框架,這些框架讓我們開發(fā)更快速、測試更便捷、拓展更簡單。
盡管 Node.js 服務(wù)端開發(fā)領(lǐng)域有著諸如 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ā),旨在構(gòu)建高效、可靠、高拓展性服務(wù)端應(yīng)用程序的先進(jìn)的 Node.js 框架。
正如 Nest 作者所言——深受 Angular 啟發(fā),所以 Nest 的開發(fā)體驗(yàn)與 Angular 有些類似。既可以使用 TypeScript(與 Angular 相同,官方推薦),也可以使用 JavaScript 構(gòu)建項(xiàng)目,同時結(jié)合了面向?qū)ο缶幊蹋∣bject Oriented Programming)、函數(shù)式編程(Functional Programming)、函數(shù)響應(yīng)式編程(Functional Reactive Programming)等元素,可兼容主流第三方庫與插件。
目前 Nest 的 Github star 數(shù)在 8000 左右,比我關(guān)注那會高了不少。
Nest 特點(diǎn)
從下圖可以看到官方宏觀概述的特點(diǎn)
高拓展性。可正常使用任何第三方庫。
多功能性。適用于多種類型的服務(wù)端應(yīng)用程序編寫。
前沿先進(jìn)。結(jié)合最前沿的 JavaScript 技術(shù)特性,將設(shè)計(jì)模式和成熟方案的理念帶入 Node.js 領(lǐng)域中。
以上是較為宏觀的特性概述,關(guān)于其技術(shù)上的特點(diǎn),我認(rèn)為較為突出的有以下幾點(diǎn):
官方推薦使用 TypeScript,工程化意義重大。
基于 Express 與 socket.io 套件,兼容其他庫。
語法風(fēng)格類似 Angular 2+,也類似 Java Spring 框架,大量使用裝飾器(ES7 Decorator)函數(shù),無侵入式的語法使得代碼邏輯更為清晰。
遵循控制反轉(zhuǎn)(IoC, Inversion of Control)思想,大量使用依賴注入(DI, Dependency Injection)的設(shè)計(jì)模式,大大降低了單元間的耦合度。
內(nèi)置的異常控制層(Exception Filter),大至應(yīng)用,小至邏輯,對各個級別的異常作捕獲與處理,在程序響應(yīng)上對用戶更為友好。
內(nèi)置的權(quán)限控制層(Guard)與攔截層(Interceptor)。
集成工程化的測試,官方使用 Jest 測試框架。
小試牛刀
我剛開始接觸 Nest 時,新建項(xiàng)目還沒有現(xiàn)在這么方便,相關(guān)依賴的安裝都比較人肉。
不過好在前不久正式版 @nestjs/cli 腳手架終于發(fā)布了(猶記得翹首以盼的我),新建項(xiàng)目也變得十分方便。
廢話,少說。腳手架,走一波。(單押 X2)
環(huán)境準(zhǔn)備
Visual Studio Code(蘿卜青菜,各有所愛)
Node.js v9.2.0(>= 8.9.0 即可)
@nestjs/cli(官方腳手架)
$ npm i -g @nestjs/cli
· nest-demo(項(xiàng)目初始化)
$ nest new nest-demo
初識 Nest
如無意外,通過腳手架初始化項(xiàng)目的過程大致如下
?? 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)建了種子項(xiàng)目并用 yarn 安裝了依賴包。如果沒有安裝成功的話,可手動進(jìn)行安裝。
我們可以看到,腳手架工具創(chuàng)建了若干文件,包括
-
根目錄
大多為配置文件,如 prettier、nodemon、tslint 等工具的配置文件(若不熟悉可以暫時忽略,將重心放在 *.ts 文件上有助于快速入手框架)
-
test 文件夾
端對端(e2e)測試用例與配置文件
-
src 文件夾
主程序入口文件及若干模塊文件。其中文件層次關(guān)系大致如下
我們對文件結(jié)構(gòu)有了初步的認(rèn)識
入口文件 main.ts 引導(dǎo)程序運(yùn)行,加載模塊 app.module.ts
模塊中,包括控制器 app.controller.ts,服務(wù)提供商 app.service.ts,測試用例 app.controller.spec.ts 等組件。
接下來我們直接以開發(fā)模式運(yùn)行程序。
$ 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
從圖中我們能夠得到的信息大致為
運(yùn)行 nodemon 監(jiān)聽項(xiàng)目文件
通過 ts-node 引導(dǎo)入口文件 main.ts
初始化模塊與路由
在 main.ts 可以看到,默認(rèn)端口號為 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 進(jìn)行 API 調(diào)試
Hello World!
看到 “Hello World! ” 的字符串,不禁讓猿熱血澎湃 —— 他做到了!
我們回過頭來看看這個值是怎么來的。
通過結(jié)構(gòu)示例圖,我們知道模塊 (module) 是一個非常重要的概念,而每個模塊的控制器 (controller) 更是扮演著運(yùn)籌帷幄的角色。
app.controller.ts
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
root(): string {
return this.appService.root();
}
}
app.controller.ts 文件中定義了一個類,通過裝飾器 @Controller() 使其成為一個路由控制類。類方法中,有構(gòu)造函數(shù) constructor 以及帶有裝飾器 @Get() 的 root 函數(shù)。
app.service.ts
@Injectable()
export class AppService {
root(): string {
return 'Hello World!';
}
}
root 函數(shù)通過調(diào)用 service 中的 root 方法,返回字符串 “Hello World!”。
此外,關(guān)于 HTTP 響應(yīng)的操作,Nest 大體上已經(jīng)幫我們安排的明明白白了,這也是官方推薦的寫法。
當(dāng)我們的函數(shù)返回 JavaScript 對象或數(shù)組時,返回值會被自動轉(zhuǎn)化成 JSON 對象;
當(dāng)我們的函數(shù)返回字符串時,返回值不作處理。
響應(yīng)狀態(tài)碼默認(rèn)情況下總是 200,除了 POST 請求為 201 外,當(dāng)然,我們是有辦法通過裝飾器輕松修改返回值的。
當(dāng)然啦,如果有倔強(qiáng)的老哥非要操作一下 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ù),且可傳遞目標(biāo)對象、名稱與屬性描述作為參數(shù)的表達(dá)式。你可以使用@字符作為前綴并將裝飾器放在你想裝飾的對象上。裝飾器可以被用在一個類或者一個屬性上。
想要了解更多關(guān)于裝飾器的知識,可以參考這篇文章或者自行搜索
https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841
裝飾器 (Decorator) 作為 ES7 的一大特性,在 TypeScript 中可以被自由運(yùn)用,在 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 以說明值的改變。
此時服務(wù)會自動重啟,我們以 GET 方式請求新地址
http://localhost:3000?name=victor
$ curl http://localhost:3000?name=Victor
Hello Victor!
Surprise!好了,一個 url 傳參的鮮活栗子就這么輕松加愉快的舉完了。
除了 @Query() 之外,Nest 內(nèi)置的還有一些裝飾器提供給我們直接使用,列舉一下
@Request() —— Express 請求對象
@Response() —— Express 響應(yīng)對象
@Session() —— Session 對象
@Param(param?: string) —— RESTful 風(fēng)格的路由參數(shù)
@Body(param?: string) —— 請求體
@Headers(param?: string) —— 請求頭
官方提供的裝飾器固然方便,然鵝在錯綜復(fù)雜的需求下,僅僅有這幾個裝飾器還是略顯不足的。在這里,我們甚至可以自定義裝飾器。
這里我們新建一個文件 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);
}
此時服務(wù)重啟,再次訪問 http://localhost:3000。
$ curl localhost:3000
Hello Victor!
通過裝飾器的作用,我們成功的將 name 的值設(shè)置成 Victor,這是一個簡單的例子,在實(shí)際生產(chǎn)環(huán)境中可以添加更為復(fù)雜的邏輯。
依賴注入
如果說裝飾器語法是 Nest 健美的身材,那么依賴注入則可以稱為 Nest 有趣的靈魂了。
簡單來說,依賴注入(DI, Dependency Injection)是一種設(shè)計(jì)模式,旨在提供對象實(shí)例,調(diào)用單元在使用依賴對象實(shí)例時無需關(guān)心其提供方式,而是統(tǒng)一由 DI 系統(tǒng)提供。
想要了解更多依賴注入知識,可以參考以下文章或者自行搜索
https://angular.io/guide/dependency-injection-pattern
事實(shí)上,Nest 的設(shè)計(jì)理念是,“萬物皆可為提供商” —— 如服務(wù)類(service)、倉庫類(repository)、工廠類(factory)、幫助類(helper)等等。
我們從實(shí)際代碼入手了解這一概念,前面提到的 @Injectable() 裝飾器,其裝飾的對象正是我們所說的“依賴對象”,也就是例子中的 service 類。
app.module.ts
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
在 module 文件中,我們聲明了 AppService 類作為 provider,這是 AppController 可以無實(shí)例化直接使用 appService 對象的伏筆。
app.controller.ts
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
root(): string {
return this.appService.root();
}
}
AppService 作為可注入的依賴對象,通過在構(gòu)造函數(shù) constructor 中聲明的方式,配合 DI 模式,解決了兩者之間的依賴關(guān)系與獨(dú)立性。于是,我們可以直接在方法中調(diào)用 appService。
app.service.ts
@Injectable()
export class AppService {
root(): string {
return 'Hello World!';
}
}
理解了依賴注入的概念,可以更好的閱讀與學(xué)習(xí) Nest 的其他內(nèi)容,如中間件(Middlewire)、管道(Pipe)、防御層(Guard)、攔截器(Interceptor)等等,都是通過依賴注入的方式與我們的控制層進(jìn)行關(guān)聯(lián)的。
實(shí)際上,配合 @Inject() 裝飾器,可以實(shí)現(xiàn)更為多樣的注入類型,這是更為高級的 provider 注入方式,由于文章篇幅原因,在此就不贅述了。
結(jié)束語
這是我的第一篇公眾號文章。
初心是為了介紹 Nest 框架,希望是不僅有落到實(shí)處的代碼示例,不至于顯得太空虛;同時也有更為重要的核心概念介紹,讓讀者有更好的大局觀與學(xué)習(xí)方向。
一篇文章往往不足以描述事物的全部,還有很多優(yōu)秀的思想與巧妙的設(shè)計(jì)等著我們?nèi)ヌ剿鳎覀兿缕恼乱姡?/p>
參考資料
原創(chuàng)作者
陳仕豪,年十八,騷話連篇笑哈哈。skr~