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

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

前言

近幾年由于 Node.js 的發(fā)展,JavaScript 變成了一種全棧通用語言探橱,同時誕生了諸如 Angular申屹、React、Vue 等一系列提高開發(fā)者生產(chǎn)力的優(yōu)秀前端項目框架隧膏,這些框架讓我們開發(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ā)體驗與 Angular 有些類似盏阶。既可以使用 TypeScript(與 Angular 相同,官方推薦)闻书,也可以使用 JavaScript 構(gòu)建項目名斟,同時結(jié)合了面向?qū)ο缶幊蹋∣bject Oriented Programming)、函數(shù)式編程(Functional Programming)魄眉、函數(shù)響應(yīng)式編程(Functional Reactive Programming)等元素砰盐,可兼容主流第三方庫與插件。

目前 Nest 的 Github star 數(shù)在 8000 左右坑律,比我關(guān)注那會高了不少岩梳。

Nest 特點

從下圖可以看到官方宏觀概述的特點

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

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

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

以上是較為宏觀的特性概述抵栈,關(guān)于其技術(shù)上的特點告材,我認(rè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è)計模式蹬挤,大大降低了單元間的耦合度缚窿。

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

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

  7. 集成工程化的測試育瓜,官方使用 Jest 測試框架葫隙。

小試牛刀

我剛開始接觸 Nest 時,新建項目還沒有現(xiàn)在這么方便躏仇,相關(guān)依賴的安裝都比較人肉恋脚。

不過好在前不久正式版 @nestjs/cli 腳手架終于發(fā)布了(猶記得翹首以盼的我),新建項目也變得十分方便焰手。

廢話糟描,少說。腳手架书妻,走一波船响。(單押 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(項目初始化)

$ 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 安裝了依賴包灿意。如果沒有安裝成功的話估灿,可手動進(jìn)行安裝。

我們可以看到缤剧,腳手架工具創(chuàng)建了若干文件馅袁,包括

  • 根目錄

    大多為配置文件,如 prettier荒辕、nodemon汗销、tslint 等工具的配置文件(若不熟悉可以暫時忽略,將重心放在 *.ts 文件上有助于快速入手框架)

  • test 文件夾

    端對端(e2e)測試用例與配置文件

  • src 文件夾

    主程序入口文件及若干模塊文件抵窒。其中文件層次關(guān)系大致如下

我們對文件結(jié)構(gòu)有了初步的認(rèn)識

  • 入口文件 main.ts 引導(dǎo)程序運行弛针,加載模塊 app.module.ts

  • 模塊中,包括控制器 app.controller.ts李皇,服務(wù)提供商 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 引導(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) 更是扮演著運籌帷幄的角色。

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)然啦,如果有倔強的老哥非要操作一下 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 中可以被自由運用,在 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贾费,這是一個簡單的例子钦购,在實際生產(chǎn)環(huán)境中可以添加更為復(fù)雜的邏輯。

依賴注入

如果說裝飾器語法是 Nest 健美的身材褂萧,那么依賴注入則可以稱為 Nest 有趣的靈魂了押桃。

簡單來說,依賴注入(DI, Dependency Injection)是一種設(shè)計模式导犹,旨在提供對象實例唱凯,調(diào)用單元在使用依賴對象實例時無需關(guān)心其提供方式,而是統(tǒng)一由 DI 系統(tǒng)提供谎痢。

想要了解更多依賴注入知識磕昼,可以參考以下文章或者自行搜索

https://angular.io/guide/dependency-injection-pattern

事實上,Nest 的設(shè)計理念是节猿,“萬物皆可為提供商” —— 如服務(wù)類(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 作為可注入的依賴對象,通過在構(gòu)造函數(shù) constructor 中聲明的方式仿野,配合 DI 模式铣减,解決了兩者之間的依賴關(guān)系與獨立性。于是脚作,我們可以直接在方法中調(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)的。

實際上从祝,配合 @Inject() 裝飾器襟己,可以實現(xiàn)更為多樣的注入類型,這是更為高級的 provider 注入方式牍陌,由于文章篇幅原因擎浴,在此就不贅述了。

結(jié)束語

這是我的第一篇公眾號文章毒涧。

初心是為了介紹 Nest 框架贮预,希望是不僅有落到實處的代碼示例,不至于顯得太空虛契讲;同時也有更為重要的核心概念介紹萌狂,讓讀者有更好的大局觀與學(xué)習(xí)方向。

一篇文章往往不足以描述事物的全部怀泊,還有很多優(yōu)秀的思想與巧妙的設(shè)計等著我們?nèi)ヌ剿鳎覀兿缕恼乱姡?/p>

參考資料

原創(chuàng)作者

陳仕豪误趴,年十八霹琼,騷話連篇笑哈哈。skr~

原創(chuàng)鏈接

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凉当,一起剝皮案震驚了整個濱河市枣申,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌看杭,老刑警劉巖忠藤,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異楼雹,居然都是意外死亡模孩,警方通過查閱死者的電腦和手機尖阔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榨咐,“玉大人介却,你說我怎么就攤上這事】樽拢” “怎么了齿坷?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長数焊。 經(jīng)常有香客問我永淌,道長,這世上最難降的妖魔是什么佩耳? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任遂蛀,我火速辦了婚禮,結(jié)果婚禮上蚕愤,老公的妹妹穿的比我還像新娘答恶。我一直安慰自己,他們只是感情好萍诱,可當(dāng)我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布悬嗓。 她就那樣靜靜地躺著,像睡著了一般裕坊。 火紅的嫁衣襯著肌膚如雪包竹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天籍凝,我揣著相機與錄音周瞎,去河邊找鬼。 笑死饵蒂,一個胖子當(dāng)著我的面吹牛声诸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播退盯,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼彼乌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了渊迁?” 一聲冷哼從身側(cè)響起慰照,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎琉朽,沒想到半個月后毒租,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡箱叁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年墅垮,在試婚紗的時候發(fā)現(xiàn)自己被綠了惕医。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡噩斟,死狀恐怖曹锨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剃允,我是刑警寧澤沛简,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站斥废,受9級特大地震影響椒楣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牡肉,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一捧灰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧统锤,春花似錦毛俏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逾雄,卻和暖如春阀溶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸦泳。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工银锻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人做鹰。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓击纬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钾麸。 傳聞我的和親對象是個殘疾皇子更振,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,107評論 2 356