原文來自卡米爾大神(Kamil My?liwiec):Nest FINAL release is here! Node.js framework built on top of TypeScript
Nest 是 Node.js圈子中又一強(qiáng)大的 Web 框架, 使用 Nest 可以輕松構(gòu)建高效可拓展應(yīng)用。 Nest 使用現(xiàn)代 JavaScript 技術(shù)構(gòu)建,編碼使用TypeScript,同時(shí)汲取了 OOP (面向?qū)ο缶幊? 以及 FP(函數(shù)式編程) 的精華所在。
Nest 不僅僅是一個 Web 框架,使用 Nest 時(shí),無需擔(dān)心有沒有豐富的社區(qū)支持, 因?yàn)镹est 本身就基于聞名遐邇的老牌框架而建 – Express 和socket.io! 因此,學(xué)習(xí) Nest 能夠讓你輕松上手,而不用學(xué)習(xí)一整套新的工具全家桶。
核心概念
Nest 的核心內(nèi)容是提供一個服務(wù)框架 (architecture), 幫助開發(fā)者實(shí)現(xiàn)多層分離,業(yè)務(wù)抽象。
安裝
$ npm install --save nest.js
配置應(yīng)用
Nest 支持 ES6 和 ES7 (decorators, async / await) 新特性。 所以最簡單方式就是使用 Babel 或者 TypeScript。
本文中會使用 TypeScript (當(dāng)然不是唯一方式),我也推薦大家使用這種方式,下邊是一個簡單的 tsconfig.json
文件
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6"
},
"exclude": [ "node_modules" ]
}
注意 emitDecoratorMetadata
以及 experimentalDecorators
需要設(shè)置為 true, 來支持項(xiàng)目中使用 Decorator 新特性。
現(xiàn)在可以寫一個小服務(wù)了。首先,創(chuàng)建一個應(yīng)用的入口模塊(entry module) (app.module.ts):
import { Module } from 'nest.js';
@Module({})
export class ApplicationModule {}
此時(shí)尚未為模塊添加修飾 metadata 是空對象 ({}), 我們現(xiàn)在只想讓服務(wù)跑起來 (現(xiàn)在也沒有控制器和組件能夠讓我們添加的).
第二步,編寫主文件:index.ts
(這個文件名隨意,約定俗成,喜歡使用 Index) ,調(diào)用 Nest 提供的工廠函數(shù)創(chuàng)建 Nest應(yīng)用實(shí)例,并將這個實(shí)例掛載在剛創(chuàng)建的入口模塊上。
import { NestFactory } from 'nest.js';
import { ApplicationModule } from './app.module';
const app = NestFactory.create(ApplicationModule);
app.listen(3000, () => console.log('Application is listening on port 3000'));
完工,齊活。跑一下,看看。
Express 實(shí)例
如果你需要控制 express 實(shí)例的生命周期,可以將創(chuàng)建的實(shí)例傳遞給工廠函數(shù) NestFactory.create()
,就成了下邊這樣
import express from 'express';
import { NestFactory } from 'nest.js';
import { ApplicationModule } from './modules/app.module';
const instance = express();
const app = NestFactory.create(ApplicationModule, instance);
app.listen(3000, () => console.log('Application is listening on port 3000'));
換句話說,你可以向工廠函數(shù)添加配置項(xiàng),來生成想要的服務(wù)實(shí)例。(例如,添加常用的插件 morgan 或者 body-parser).
第一個控制器
控制器層用來處理 HTTP 請求. Nest 中,控制器是用 @Controller()
修飾的類。

上一節(jié)中,我們已經(jīng)寫好了程序入口,基于此,我們來構(gòu)建一個應(yīng)用端口 /users
import { Controller, Get, Post } from 'nest.js';
@Controller()
export class UsersController {
@Get('users')
getAllUsers() {}
@Get('users/:id')
getUser() {}
@Post('users')
addUser() {}
}
正如所見,我們快速創(chuàng)建了同一個端口的3個應(yīng)用路徑:
GET: users
GET: users/:id
POST: users
看看剛創(chuàng)建的這個類,有木有可優(yōu)化的地方呢? users
這個詞貌似有點(diǎn)重復(fù),能優(yōu)化么?當(dāng)然!只需要向 @Controller()
添加元數(shù)據(jù)配置即可,這里添加一個路徑,重寫一下這個類。
@Controller('users')
export class UsersController {
@Get()
getAllUsers(req, res, next) {}
@Get('/:id')
getUser(req, res, next) {}
@Post()
addUser(req, res, next) {}
}
世界忽然清凈了。這里多寫了一點(diǎn),那就是所有的 Nest 控制器默認(rèn)包含 Express 自帶參數(shù)列表和參數(shù)方法。如果你想學(xué)習(xí)更多 req (request), res (response) 以及 next 的使用 你需要讀一下 Express 路由相關(guān)的文檔。在 Nest 中,這些參數(shù)能力依舊,不僅如此,Nest 還對其進(jìn)行了拓展。 Nest提供了一整套網(wǎng)絡(luò)請求裝飾器,你可以用這些裝飾器來增強(qiáng)請求參數(shù)處理。
Nest | Express |
---|---|
@Request() | req |
@Response() | res |
@Next() | next |
@Session() | req.session |
@Param(param?: string) | req.params[param] |
@Body(param?: string) | req.body[param] |
@Query(param?: string) | req.query[param] |
@Headers(param?: string) | req.headers[param] |
簡單 demo 這些裝飾器的用法
@Get('/:id')
public async getUser(@Response() res, @Param('id') id) {
const user = await this.usersService.getUser(id);
res.status(HttpStatus.OK).json(user);
}
使用這些裝飾器的時(shí)候,記得從 Nest 引入 Param 模塊
import { Response, Param } from 'nest.js';
現(xiàn)在 UsersController
已經(jīng)可以投放使用了,但模塊并不知道有這個控制器的存在,所以進(jìn)入 ApplicationModule
給裝飾器添加些元數(shù)據(jù)。
import { Module } from 'nest.js';
import { UsersController } from "./users.controller";
@Module({
controllers: [ UsersController ]
})
export class ApplicationModule {}
正如所見,引入控制器,并將其添加到元數(shù)據(jù)的 controllers
數(shù)組,就完成了注冊。
組件
Nest 中所有內(nèi)容都可以視為組件 Service, Repository, Provider 等都是組件。同時(shí)組件可以相互嵌套,也可以作為服務(wù)從構(gòu)造函數(shù)里注入控制器。

上一節(jié),我們創(chuàng)建了一個簡單的控制器,UsersController
。這個控制器負(fù)責(zé)響應(yīng)網(wǎng)絡(luò)請求并將請求映射到數(shù)據(jù)(當(dāng)然現(xiàn)在還是假數(shù)據(jù),但并不影響)上。實(shí)際上,這并不是好的實(shí)踐,數(shù)據(jù)和請求分離才更便于分離依賴,抽象業(yè)務(wù)。所以應(yīng)該將控制器的職責(zé)單一化,只負(fù)責(zé)響應(yīng)網(wǎng)絡(luò)請求,而將更復(fù)雜的任務(wù)交付給 服務(wù) 來實(shí)現(xiàn)。我們稱這個抽離出來的依賴叫 UsersService
組件。實(shí)際開發(fā)中,UsersService
應(yīng)從持久層調(diào)用方法實(shí)現(xiàn)數(shù)據(jù)整合,比如從 UsersRepository
組件,為了說明簡單,我們暫時(shí)跳過這個階段,依然使用假數(shù)據(jù)來說明組件的概念。
import { Component, HttpException } from 'nest.js';
@Component()
export class UsersService {
private users = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Alice Caeiro" },
{ id: 3, name: "Who Knows" },
];
getAllUsers() {
return Promise.resolve(this.users);
}
getUser(id: number) {
const user = this.users.find((user) => user.id === id);
if (!user) {
throw new HttpException("User not found", 404);
}
return Promise.resolve(user);
}
addUser(user) {
this.users.push(user);
return Promise.resolve();
}
}
Nest 組件也是個類,他使用 @Component()
裝飾器來修飾。 正如所見,getUser()
方法中調(diào)用了 HttpException
模塊,這是 Nest 內(nèi)置的 Http 請求異常處理模塊,傳入兩個參數(shù),錯誤信息和返給客戶端的狀態(tài)碼。 優(yōu)先處理異常是一種優(yōu)良習(xí)慣,更多情況可以拓展 HttpException
來實(shí)現(xiàn) (更多細(xì)節(jié)查看錯誤處理小結(jié))。如此,我們的用戶服務(wù)已經(jīng)就緒,那就在用戶控制器中使用起來吧。
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
getAllUsers(@Response res) {
this.usersService.getAllUsers()
.then((users) => res.status(HttpStatus.OK).json(users));
}
@Get('/:id')
getUser(@Response() res, @Param('id') id) {
this.usersService.getUser(+id)
.then((user) => res.status(HttpStatus.OK).json(user));
}
@Post()
addUser(@Response() res, @Body('user') user) {
this.usersService.addUser(req.body.user)
.then((msg) => res.status(HttpStatus.CREATED).json(msg));
}
}
注意,這里沒有寫 next
參數(shù),因?yàn)檫@個場景中并未用到。
正如所見,UsersService
會自動注入控制器,這是 TypeScript 提供的語法糖,讓你輕而易舉的實(shí)現(xiàn)依賴注入,Nest 能夠從這種方式中識別出對應(yīng)的依賴。
constructor(private usersService: UsersService)
如上,即可。再次聲明,如果你在自己 demo 時(shí)如果報(bào)錯,請查看 tsconfig.json
文件中是否將 emitDecoratorMetadata
置為 true, 當(dāng)然,你從頭就沒用 ts,無可厚非,就像你非得滿足 IE6用戶需求一樣,Nest 也提供了相應(yīng)的寫法。
import { Dependencies, Controller, Post, Get, HttpStatus, Response, Param, Body } from 'nest.js';
@Controller('users')
@Dependencies(UsersService)
export class UsersController {
constructor(usersService) {
this.usersService = usersService;
}
@Get()
getAllUsers(@Response() res) {
this.usersService.getAllUsers()
.then((users) => res.status(HttpStatus.OK).json(users));
}
@Get('/:id')
getUser(@Response() res, @Param('id') id) {
this.usersService.getUser(+id)
.then((user) => res.status(HttpStatus.OK).json(user));
}
@Post()
addUser(@Response() res, @Body('user') user) {
this.usersService.addUser(user)
.then((msg) => res.status(HttpStatus.CREATED).json(msg));
}
}
也不難是吧,眼熟不?當(dāng)然,沒有 ts 之前,我項(xiàng)目里絕大多數(shù)依賴注入都是如此實(shí)現(xiàn)的。不過,當(dāng)你運(yùn)行項(xiàng)目的時(shí)候,又要報(bào)錯了,因?yàn)?Nest 和 ApplicationModule
并不知道 UserService
是什么東西,需要像添加控制器一樣,將組件(服務(wù),提供商,持久層)也添加到模塊里,統(tǒng)一叫做組件。
import { Module } from 'nest.js';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [ UsersController ],
components: [ UsersService ]
})
export class ApplicationModule {}
齊活!現(xiàn)在應(yīng)用能夠正常跑了,不過路由 addUser
貌似不太管用, 為啥呢?因?yàn)槲覀儾⒉荒苷_讀取 req.body.user
啊,這時(shí)候咋整呢?這一步的思考至關(guān)重要,是自己造輪子解決這個問題,還是用別人的輪子?首先告訴你有 body-parser
這個express中間件 解決這個問題,你可以在 express 實(shí)例中使用這些插件。當(dāng)然,你可以自己寫一個組件來處理這個問題(從用輪子到造輪子)。
讓我們來裝上這個神奇的插件
$ npm install --save body-parser
在 express
實(shí)例中使用起來。
import express from 'express';
import * as bodyParser from 'body-parser';
import { NestFactory } from 'nest.js';
import { ApplicationModule } from './modules/app.module';
const instance = express();
instance.use(bodyParser.json());
const app = NestFactory.create(ApplicationModule, instance);
app.listen(3000, () => console.log('Application is listening on port 3000'));
異步回調(diào)方案 Async/await (ES7)
Nest 支持使用 ES7的 async / await 解決異步回調(diào)問題, 所以,我們可以順利的重構(gòu) userController
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
async getAllUsers(@Response() res) {
const users = await this.usersService.getAllUsers();
res.status(HttpStatus.OK).json(users);
}
@Get('/:id')
async getUser(@Response() res, @Param('id') id) {
const user = await this.usersService.getUser(+id);
res.status(HttpStatus.OK).json(user);
}
@Post()
async addUser(@Response() res, @Body('user') user) {
const msg = await this.usersService.getUser(user);
res.status(HttpStatus.CREATED).json(msg);
}
}
這樣能看起來舒服一點(diǎn)是吧,至少 async/await 要比 promise/then 方案更酷炫一些。更多可以去看 async / await.
模塊
模塊是用 @Module({}) 裝飾器修飾的類。Nest 會使用這些元數(shù)據(jù)來組織模塊的結(jié)構(gòu)。

截止到現(xiàn)在,我們的 ApplicationModule
看上去是這樣的。
import { Module } from 'nest.js';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [ UsersController ],
components: [ UsersService ]
})
export class ApplicationModule {}
默認(rèn)情況下,模塊封裝了所有的依賴,換句話說,控制器和組件對于模塊外部是透明的。模塊之間可以相互引用,實(shí)際上,Nest 本身也是一個模塊樹。基于最佳實(shí)踐,我們將 UsersController
和 UsersService
轉(zhuǎn)移到 UsersModule
,新建一個 users.module.ts
來存儲這個模塊。
import { Module } from 'nest.js';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [ UsersController ],
components: [ UsersService ]
})
export class UsersModule {}
然后在主模塊 ApplicationModule
引入 UsersModule
,并在元數(shù)據(jù)中使用 modules 聲明。
import { Module } from 'nest.js';
import { UsersModule } from './users/users.module';
@Module({
modules: [ UsersModule ]
})
export class ApplicationModule {}
齊活!正如所見,你可以根據(jù)業(yè)務(wù)需求,將部分模塊抽離出來做成可重用的模塊。
依賴注入
模塊可以輕松的注入她自己的組件,在元數(shù)據(jù)里說明一下就行了。
@Module({
controllers: [ UsersController ],
components: [ UsersService, ChatGateway ]
})
export class UsersModule implements NestModule {
constructor(private usersService: UsersService) {}
}
與此同時(shí),組件也可以注入模塊
export class UsersController { constructor(private module: UsersModule) {}}
中間件
中間件本質(zhì)是一個函數(shù),這個函數(shù)在路由處理器之前調(diào)用。中間件能夠查看請求和回復(fù)對象,同時(shí)也能修改這些內(nèi)容。你可以把他們理解為 圍欄。如果中間件不放行,請求是不會抵達(dá)路由處理控制器的。

我們來構(gòu)建一個虛擬的授權(quán)中間件(簡單起見,只驗(yàn)證 username)。我們使用 HTTP 的頭部信息 X-Access-Token
來提供 username(這種實(shí)現(xiàn)很奇怪,不過例子嘛,不要太認(rèn)真)
import { UsersService } from './users.service';
import { HttpException, Middleware, NestMiddleware } from 'nest.js';
@Middleware()
export class AuthMiddleware implements NestMiddleware {
constructor(private usersService: UsersService) {}
resolve() {
return (req, res, next) => {
const userName = req.headers["x-access-token"];
const users = this.usersService.getUsers();
const user = users.find((user) => user.name === userName);
if (!user) {
throw new HttpException('User not found.', 401);
}
req.user = user;
next();
}
}
}
關(guān)于中間件,你需要知道的只有四條
- 你使用 @Middleware() 裝飾器告訴 Nest,下邊這個類是個中間件
- 你可以使用 NestMiddleware 模塊提供的接口,這樣你就可以將注意力集中在輸入輸出上了,換句話說,你只需要實(shí)現(xiàn) resolve()函數(shù)
- 像組件一樣,中間件可以通過構(gòu)造函數(shù)注入其他組件作為依賴,這里的組件應(yīng)該是模塊內(nèi)部的。
- 中間件必須包含一個 resolve()函數(shù), 同時(shí)return 一個新的函數(shù)(高階函數(shù))為啥這么設(shè)計(jì)呢?因?yàn)榇罅康?express 第三方插件都是這么搞,Nest 也支持的話,就能夠讓大家輕松平移過來了。
注意: 上邊代碼中子在
UsersService
添加了一個getUsers
方法,用以封裝獲取所有用戶。
現(xiàn)在我們有這個中間件了,嘗試用起來吧。
import { Module, MiddlewaresConsumer } from 'nest.js';
@Module({
controllers: [ UsersController ],
components: [ UsersService ],
exports: [ UsersService ]
})
export class UsersModule {
configure(consumer: MiddlewaresConsumer) {
consumer.apply(AuthMiddleware)
.forRoutes(UsersController);
}
}
如上所見,在類 UsersModule
內(nèi)部調(diào)用 configure()
方法。方法有一個參數(shù),通過參數(shù)配置中間件消費(fèi)者 MiddlewaresConsumer
, 這是一個用于配置中間件的對象。這個對象有 apply()
方法,可以接收逗號分隔的多個中間件,apply()
方法返回一個對象,有兩個內(nèi)置方法
- forRoutes() – 將中間件配置給哪個控制器使用,可以用逗號分隔多個
- with – 向中間件反向傳遞參數(shù)
原理是啥呢?
當(dāng)把 UsersController
作為參數(shù)傳遞給 forRoutes
方法時(shí),Nest 會自動為控制器配置中間件。
GET: users
GET: users/:id
POST: users
也可通過配置的方式進(jìn)行逐個操作,告知中間件作用在哪個路由上。一般用于指明某一個認(rèn)證作用于全部控制器的場景。
consumer.apply(AuthMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL });
鏈?zhǔn)讲僮?/h4>
Nest 支持鏈?zhǔn)讲僮鳎憧梢暂p松實(shí)現(xiàn)以下代碼
consumer.apply(AuthMiddleware, PassportMidleware)
.forRoutes(UsersController, OrdersController, ClientController)
.apply(...)
.forRoutes(...);
調(diào)用順序
中間件按照其出現(xiàn)在數(shù)組中的順序依次調(diào)用,子模塊的中間件會在父模塊中間件調(diào)用之后調(diào)用。
基于網(wǎng)關(guān)的實(shí)時(shí)應(yīng)用
Nest 中有一種獨(dú)特的組件叫做網(wǎng)關(guān)。網(wǎng)關(guān)可以幫助開發(fā)者構(gòu)造實(shí)時(shí)應(yīng)用。Nest 在實(shí)現(xiàn)過程中封裝了 socket.io中的部分特性。

網(wǎng)關(guān)也是組件,一樣可以通過構(gòu)造函數(shù)注入依賴,當(dāng)然也可作為其他組件的依賴。
import { WebSocketGateway, OnGatewayInit } from 'nest.js/websockets';
@WebSocketGateway()
export class UsersGateway implements OnGatewayInit {
afterInit(server) {}
handleConnection(client) {}
handleDisconnect(client) {}
}
默認(rèn)情況下,Websocket 會開通80端口,并使用默認(rèn)命名空間,可以通過向裝飾器傳遞元數(shù)據(jù)的方式進(jìn)行配置。
@WebSocketGateway({ port: 81, namespace: 'users' })
當(dāng)然,想要網(wǎng)關(guān)生效,還需要將其加入主模塊,跟其他服務(wù)組件并列。
@Module({
controllers: [ UsersController ],
components: [ UsersService, UsersGateway ],
exports: [ UsersService ]
})
另外,網(wǎng)關(guān)有三個重要的生命周期鉤子:
- afterInit 有個本地服務(wù)器 socket.io 對象可以操作,一般寫為 server
- handleConnection/handleDisconnect 有個本地客戶端 socket.io 對象可以操作,一般寫為 client
- OnGatewayInit/OnGatewayConnection/OnGatewayDisconnect 網(wǎng)關(guān)狀態(tài)管理接口
如何傳遞數(shù)據(jù)
在網(wǎng)關(guān)中可以輕松訂閱廣播信息,使用 SubscribeMessage
裝飾器實(shí)現(xiàn)。
import { WebSocketGateway, OnGatewayInit, SubscribeMessage } from 'nest.js/websockets';
@WebSocketGateway({ port: 81, namespace: 'users' })
export class UsersGateway implements OnGatewayInit {
afterInit(server) {}
handleConnection(client) {}
handleDisconnect(client) {}
@SubscribeMessage('drop')
handleDropMessage(sender, data) { // sender 是本地客戶端 socket.io 對象}
}
客戶端可以輕松實(shí)現(xiàn)
import * as io from 'socket.io-client';
const socket = io('http://URL:PORT/');
socket.emit('drop', { msg: 'test' });
@WebSocketServer()
如果你想準(zhǔn)確的指明使用哪個 socket.io 本地服務(wù)實(shí)例,可以將這個實(shí)例通過 @WebSocketServer()
裝飾器進(jìn)行包裹。
import { WebSocketGateway, WebSocketServer, OnGatewayInit } from 'nest.js/websockets';
@WebSocketGateway({ port: 81, namespace: 'users' })
export class UsersGateway implements OnGatewayInit {
@WebSocketServer() server;
afterInit(server) {}
@SubscribeMessage('drop')
handleDropMessage(sender, data) { // sender是本地客戶端 socket.io 對象}
}
服務(wù)端初始化以后便完成指定。
網(wǎng)關(guān)中間件
網(wǎng)關(guān)中間件與路由中間件相似。中間件是一個函數(shù),在訂閱事件觸發(fā)之前調(diào)用。網(wǎng)關(guān)可以操作本地 socket 對象。你可以把他們理解為 圍欄。如果中間件不放行,信息是不會被廣播的。
@Middleware()
export class AuthMiddleware implements GatewayMiddleware {
public resolve(): (socket, next) => void {
return (socket, next) => {
console.log('Authorization...'); next();
}
}
}
網(wǎng)關(guān)中間件須知須會:(跟中間件一節(jié)一毛一樣,我是拖戲的,騙稿費(fèi)的)
- 你需要通過
@Middeware
告知 Nest 這是一個中間件 - 你可以繼承自
GatewayMiddleware
然后集中處理 resolve 中的內(nèi)容 - 像組件一樣,中間件可以通過構(gòu)造函數(shù)注入其他組件作為依賴,這里的組件應(yīng)該是模塊內(nèi)部的。
- 中間件必須包含一個 resolve()函數(shù), 同時(shí)return 一個新的函數(shù)(高階函數(shù))
中間件搞好了,就用起來。
@WebSocketGateway({
port: 2000,
middlewares: [ ChatMiddleware ]
})
export class ChatGateway {}
如上所見,網(wǎng)關(guān)@WebSocketGateway()
接收元數(shù)據(jù)來配置使用的中間件,這里可以配置一組中間件,他們有個名,叫做“事前諸葛亮”
交互流
如上所見,Nest 中的網(wǎng)關(guān)也是組價(jià),可以作為依賴注入其他組件,這就能讓我們針對不同的信息作出不同的操作。比如做信息過濾(fuck=>fu*k),當(dāng)然也可將組件注入網(wǎng)關(guān),將網(wǎng)關(guān)的功能再次封裝拆分。有個話題專門解決這個方向,網(wǎng)關(guān)交互流,你可以去這里查看詳情
微服務(wù)
將 Nest 應(yīng)用服務(wù)轉(zhuǎn)為微服務(wù)異常簡單。創(chuàng)建服務(wù)應(yīng)用的時(shí)候這么寫:
const app = NestFactory.create(ApplicationModule);
app.listen(3000, () => console.log('Application is listening on port 3000'));
轉(zhuǎn)成微服務(wù),只需要改一個方法即可,Java 工程師,PHP?看一看!
const app = NestFactory.createMicroservice(ApplicationModule, { port: 3000 });
app.listen() => console.log('Microservice is listening on port 3000'));
使用 TCP 進(jìn)行通信

默認(rèn)情況下,Nest 的微服務(wù)監(jiān)聽 TCP 協(xié)議信息,于是微服務(wù)中 @RequestMapping()
(以及 @Post()
, @Get()
) 這些裝飾器就用不到了,因?yàn)樗麄兓?HTTP協(xié)議。微服務(wù)中如何識別信息呢?Nest 提供了 patterns,這可能是對象,字符串,甚至數(shù)字(當(dāng)然,數(shù)字不太好)
import { MessagePattern } from 'nest.js/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'add' })
add(data, respond) {
const numbers = data || [];
respond(null, numbers.reduce((a, b) => a + b));
}
}
如上所見,如果你想處理信息,那么只需使用 @MessagePattern(pattern)
裝飾器,裝飾器中的元數(shù)據(jù)cmd指明了處理數(shù)據(jù)的方式,默認(rèn)傳遞兩個參數(shù)
- data, 其他微服務(wù)傳遞過來的數(shù)據(jù),或者網(wǎng)絡(luò)數(shù)據(jù)
- respond 響應(yīng)方法,默認(rèn)傳遞兩個參數(shù)(錯誤,響應(yīng))
客戶端
能夠接收數(shù)據(jù)還沒完事,你得知道怎么發(fā)送數(shù)據(jù)。在發(fā)送之前,你需要告訴 Nest 你給誰發(fā)數(shù)據(jù)。實(shí)際上也很簡單,首先你腦子里需要構(gòu)建一個 server-client 鏈接,你數(shù)據(jù)的接收方就是你的 Client,你可以使用 @Client
來裝飾
import { Controller } from 'nest.js';
import { Client, ClientProxy, Transport } from 'nest.js/microservices';
@Controller()
export class ClientController {
@Client({
transport: Transport.TCP,
port: 5667
})
client: ClientProxy;
}
@Client() 裝飾器接收三個元數(shù)據(jù)配置項(xiàng)
- transport – 傳遞數(shù)據(jù)的通道和協(xié)議,可以是
Transport.TCP
或者Transport.REDIS
,默認(rèn)為 TCP - url – 只有傳遞通道為 Redis 的時(shí)候用到(默認(rèn) – redis://localhost:6379),
- port - 端口號(默認(rèn) 3000).
創(chuàng)建客戶端
創(chuàng)建一個端口服務(wù)來驗(yàn)證一下剛剛創(chuàng)建的 Server-Client 微服務(wù)通道
import { Controller, Get } from 'nest.js';
import { Client, ClientProxy, Transport } from 'nest.js/microservices';
@Controller()
export class ClientController {
@Client({
transport: Transport.TCP,
port: 5667
})
client: ClientProxy;
@Get('client')
sendMessage(req, res) {
const pattern = { command: 'add' };
const data = [ 1, 2, 3, 4, 5 ];
this.client.send(pattern, data)
.catch((err) => Observable.empty())
.subscribe((result) => res.status(200).json({ result }));
}
}
如上所見,如果你想發(fā)送信息,你需要調(diào)用 send 方法,發(fā)送數(shù)據(jù)被封裝為參數(shù),一個 pattern一個 data.這個方法返回了 Rxjs 式的觀察者對象。這可是不得了的一個特性,因?yàn)榻换ナ接^察者對象提供了一整套炫酷的方法。比如 combine
, zip
, retryWhen
, timeout
等等,當(dāng)然你還是喜歡用 Promises-then
那套,你可以使用 toPromise()
方法。由此,當(dāng)請求發(fā)送到此控制器時(shí),就會得到下邊的結(jié)果(前提是微服務(wù)和 Web應(yīng)用都啟動起來)
{ "result": 15}
Redis
上文書 Nest 微服務(wù)還能鏈接 Redis,與 TCP 通信不同,與 Redis鏈接就能使用其自帶的各種特性,最贊的當(dāng)屬 – 發(fā)布/訂閱模式.

當(dāng)然,你得先安裝 Redis.
創(chuàng)建 Redis 微服務(wù)
創(chuàng)建一個 Redis 微服務(wù)也很簡單,在創(chuàng)建微服務(wù)時(shí),傳遞 Redis 服務(wù)配置即可。
const app = NestFactory.createMicroservice(
MicroserviceModule, {
transport: Transport.REDIS,
url: 'redis://localhost:6379'
});
app.listen(() => console.log('Microservice listen on port:', 5667 ));
如此便鏈接了 Redis,其服務(wù)中心分發(fā)的信息就能夠被此微服務(wù)捕獲到。剩下的工作跟 TCP 微服務(wù)一樣。
Redis 客戶端
現(xiàn)在仿照微服務(wù)創(chuàng)建一個客戶端,在 TCP 微服務(wù)中,你的客戶端配置像下邊這樣
@Client({
transport: Transport.TCP,
port: 5667
})
client: ClientProxy;
Redis 微服務(wù)的客戶端修改一下即可,添加一個 url 配置項(xiàng)
@Client({
transport: Transport.REDIS,
url: 'redis://localhost:6379'
})
client: ClientProxy;
是不是很簡單,數(shù)據(jù)操作和管道跟 TCP的微服務(wù)一致。
模塊共享
Nest 可以指明向外暴漏哪些組件,換句話說,我們能夠輕松地在模塊之間共享組件實(shí)例。更好的方式當(dāng)然是使用 Nest 內(nèi)置的 Shared 模塊.

以共享 ChatGateway
組件為例,添加一個 exports
關(guān)鍵字來指明導(dǎo)出的組件服務(wù)。
@Module({
components: [ ChatGateway ],
exports: [ ChatGateway ]
})
export class SharedModule {}
完事兒作為依賴注入到其他模塊就行了。
@Module({
modules: [ SharedModule ]
})
export class FeatureModule {}
完工齊活,你可以在 FeatureModule 中直接使用 ChatGateway,就跟自己模塊內(nèi)部一毛一樣。
依賴注入
依賴注入是一個強(qiáng)大的特性,能夠幫我們輕松管理類的依賴。在強(qiáng)類型語言,比如 Java 和 .Net中,依賴注入很常見。在 Nodejs 中,這種特性并不重要,因?yàn)槲覀円呀?jīng)擁有完善的模塊加載機(jī)制。例如,我可以不費(fèi)吹灰之力,從另外一個文件中引入一個模塊。模塊加載機(jī)制對于中小型應(yīng)用已經(jīng)足夠了。但隨著代碼量逐步增長,系統(tǒng)層級復(fù)雜以后,通過引用的方式管理他們之間的依賴會越來越麻煩。現(xiàn)在沒錯,不代表未來不會錯。所以自動化實(shí)現(xiàn)依賴管理呼之欲出。

這種方式尚不如通過構(gòu)造函數(shù)導(dǎo)入依賴更加清晰明了,這也是 Nest 為何內(nèi)置依賴注入機(jī)制的緣故。
自定義組件
依上文,你可以輕松將控制器綁定到模塊,這種注入方式異常簡單
@Module({
controllers: [ UsersController ],
components: [ UsersService ]
})
實(shí)際開發(fā)中,注入到模塊的可能并非指明的這些關(guān)鍵字。
useValue
const value = {};
@Module({
controllers: [ UsersController ],
components: [ {
provide: UsersService,
useValue: value } ]
})
使用場景
- 用于向服務(wù)插值,如此一來,
UsersService
的元數(shù)據(jù)中就會有 value這個值, - 用于單元測試,注入初始值
useClass
@Component()
class CustomUsersService {}
@Module({
controllers: [ UsersController ],
components: [ {
provide: UsersService,
useClass: CustomUsersService } ]
})
使用場景
- 向指定的服務(wù)注入特定的類。
useFactory
@Module({
controllers: [ UsersController ],
components: [
ChatService,
{
provide: UsersService,
useFactory: (chatService) => { return Observable.of('value');},
inject: [ ChatService ]
}]
})
使用場景
- 使用其他組件之前需要計(jì)算得到一個值,并注入這個組件
- 服務(wù)的啟動依賴一個異步值,比如讀寫文件或者鏈接數(shù)據(jù)庫
注意
- 如果想要調(diào)用其他模塊的內(nèi)部控制器和組件,需要注明引用
自定義提供者
@Module({
controllers: [ UsersController ],
components: [ { provide: 'isProductionMode', useValue: false } ]
})
使用場景
- you want to provide value with a chosen key.
注意
- it is possible to use each types useValue, useClass and useFactory.
使用方法
To inject custom provided component / value with chosen key, you have to tell Nest about it, just like that:
import { Component, Inject } from 'nest.js';
@Component()
class SampleComponent {
constructor(@Inject('isProductionMode') private isProductionMode: boolean) {
console.log(isProductionMode); // false
}
}
ModuleRef
Sometimes you might want to directly get component instance from module reference. It not a big thing with Nest – just inject ModuleRef in your class:
export class UsersController { constructor( private usersService: UsersService, private moduleRef: ModuleRef) {}}
ModuleRef 提供了一個方法:
-
get<T>(key), which returns instance for equivalent key (mainly metatype). Example moduleRef.get<UsersService>(UsersService) returns instance of UsersService component from current module.
moduleRef.get<UsersService>(UsersService)
It returns instance of UsersService component from current module.
測試
Nest gives you a set of test utilities, which boost application testing process. There are two different approaches to test your components and controllers – isolated tests or with dedicated Nest test utilities.
單元測試
Both Nest controllers and components are a simple JavaScript classes. Itmeans, that you could easily create them by yourself:
const instance = new SimpleComponent();
If your class has any dependency, you could use test doubles, for example from such libraries as Jasmine or Sinon:
const stub = sinon.createStubInstance(DependencyComponent);
const instance = new SimpleComponent(stub);
Nest 測試套件
The another way to test your applications building block is to use dedicated Nest Test Utilities. Those Test Utilities are placed in static Test class (nest.js/testing module).
import { Test } from 'nest.js/testing';
這個模塊類提供了兩個方法
- createTestingModule(metadata: ModuleMetadata), which receives as an parameter simple module metadata (the same as Module() class). This method creates a Test Module (the same as in real Nest Application) and stores it in memory.
- get<T>(metatype: Metatype<T>), which returns instance of chosen (metatype passed as parameter) controller / component (or null if it is not a part of module.
例如:
Test.createTestingModule({ controllers: [ UsersController ], components: [ UsersService ]});const usersService = Test.get<UsersService>(UsersService);
Mocks, spies, stubs
Sometimes you might not want to use a real instance of component / controller. Instead of this – you can use test doubles or custom values / objects.
const mock = {};
Test.createTestingModule({
controllers: [ UsersController ],
components: [ { provide: UsersService, useValue: mock } ]
});
const usersService = Test.get<UsersService>(UsersService); // mock
異常過濾器
With Nest you can move exception handling logic to special classes called Exception Filters. Let’s take a look at following code:
@Get('/:id')
public async getUser(@Response() res, @Param('id') id) {
const user = await this.usersService.getUser(id);
res.status(HttpStatus.OK).json(user);
}
Imagine that usersService.getUser(id) method could throws UserNotFoundException. What’s now? We have to catch an exception in route handler:
@Get('/:id')
public async getUser(@Response() res, @Param('id') id) {
try {
const user = await this.usersService.getUser(id);
res.status(HttpStatus.OK).json(user);
} catch(exception) {
res.status(HttpStatus.NOT_FOUND).send();
}
}
總的來說,我們在可能出現(xiàn)異常的地方添加 try...catch
語句塊,更好的方式當(dāng)然是使用內(nèi)置的異常過濾器。
import { Catch, ExceptionFilter, HttpStatus } from 'nest.js';
export class UserNotFoundException {}
export class OrderNotFoundException {}
@Catch(UserNotFoundException, OrderNotFoundException)
export class NotFoundExceptionFilter implements ExceptionFilter {
catch(exception, response) {
response.status(HttpStatus.NOT_FOUND).send();
}
}
可以在控制器中加入這個異常過濾器了。
import { ExceptionFilters } from 'nest.js';
@ExceptionFilters(NotFoundExceptionFilter)
export class UsersController {}
此時(shí),如果找不到用戶就會調(diào)起這個異常。
其他異常過濾器
Each controller may has infinite count of exception filters (just separate them by comma).
@ExceptionFilters(NotFoundExceptionFilter, AnotherExceptionFilter)
異常捕獲的依賴注入
異常過濾器與組件一樣工作,所以可以通過構(gòu)造函數(shù)注入依賴
HttpException
注意,這個異常模塊主要用于 RESTful API 構(gòu)造
Nest 內(nèi)置了錯誤處理層,可以捕獲所有未處理的異常,如果拋出一個非 HttpException
,Nest 會自動處理并返回下邊這個 Json 對象(狀態(tài)碼 500):
{ "message": "Unkown exception"}
異常層次結(jié)構(gòu)
在你的應(yīng)用中,你應(yīng)該創(chuàng)建你自己的錯誤層級。所有 HTTP異常相關(guān)都可以繼承自內(nèi)置 HttpException
,例如,你可以創(chuàng)建 NotFoundException 和 UserNotFoundException。
import { HttpException } from 'nest.js';
export class NotFoundException extends HttpException {
constructor(msg: string | object) {
super(msg, 404);
}
}
export class UserNotFoundException extends NotFoundException {
constructor() {
super('User not found.');
}
}
當(dāng)你項(xiàng)目中出現(xiàn)這兩個異常的時(shí)候,Nest會調(diào)起你自己配置的這個異常處理句柄。
{ "message": "User not found."}
如此一來,你可以將將精力集中在業(yè)務(wù)邏輯和代碼實(shí)現(xiàn)上了。