你想要的,Nest 都給你安排得明明白白

今天的主題不是雀巢咖啡,也不是雀巢奶粉,畢竟我王境澤就是餓死,也不會打你們一點(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)

  1. 高拓展性。可正常使用任何第三方庫。

  2. 多功能性。適用于多種類型的服務(wù)端應(yīng)用程序編寫。

  3. 前沿先進(jìn)。結(jié)合最前沿的 JavaScript 技術(shù)特性,將設(shè)計(jì)模式和成熟方案的理念帶入 Node.js 領(lǐng)域中。

以上是較為宏觀的特性概述,關(guān)于其技術(shù)上的特點(diǎn),我認(rèn)為較為突出的有以下幾點(diǎn):

  1. 官方推薦使用 TypeScript,工程化意義重大。

  2. 基于 Express 與 socket.io 套件,兼容其他庫。

  3. 語法風(fēng)格類似 Angular 2+,也類似 Java Spring 框架,大量使用裝飾器(ES7 Decorator)函數(shù),無侵入式的語法使得代碼邏輯更為清晰。

  4. 遵循控制反轉(zhuǎn)(IoC, Inversion of Control)思想,大量使用依賴注入(DI, Dependency Injection)的設(shè)計(jì)模式,大大降低了單元間的耦合度。

  5. 內(nèi)置的異常控制層(Exception Filter),大至應(yīng)用,小至邏輯,對各個級別的異常作捕獲與處理,在程序響應(yīng)上對用戶更為友好。

  6. 內(nèi)置的權(quán)限控制層(Guard)與攔截層(Interceptor)。

  7. 集成工程化的測試,官方使用 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~

原創(chuàng)鏈接

https://mp.weixin.qq.com/s/LKCecn2z1Pln90atQ0kcWw

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評論 2 380