本文爲譯文,原文地址:https://kamilmysliwiec.com/nest-final-release-is-here-node-js-framework-built-top-of-typescript,做者,@Kamil Myśliwiechtml
上篇文章地址爲: http://www.javashuo.com/article/p-xbnqlvsm-md.htmlnode
中間件是一個在路由處理程序前被調用的函數。中間件函數能夠訪問請求和響應對象,所以能夠修改它們。它們也能夠向一個屏障,若是中間件函數不調用 next()
,請求將永遠不會被路由處理程序處理。git
讓咱們來構建一個虛擬受權中間件。咱們將使用 X-Access-Token
HTTP 頭來提供用戶名(這是個奇怪的想法,可是不重要)。github
import { UsersService } from './users.service'; import { HttpException } from '@nestjs/core'; import { Middleware, NestMiddleware } from '@nestjs/common'; @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(); } } }
一些事實以下:web
你應該使用 @Middleware()
註解來告訴 Nest,這個類是一箇中間件,redis
你可使用 NestMiddleware
界面,這強制你使用 resolve()
方法,typescript
中間件(與組件相同)能夠經過其構造函數的注入依賴項(依賴關係必須是模塊的一部分),數據庫
中間件必須有 resolve()
方法,它必須返回另外一個函數(高階函數)。爲何?由於有不少第三方插件準備使用 express
中間件,你能夠簡單地在 Nest 中使用,還要感謝這個方案。express
好了,咱們已經準備好了中間件,可是咱們並無在任何地方使用它。咱們來設置它:npm
import { Module, MiddlewaresConsumer } from '@nestjs/common'; @Module({ controllers: [ UsersController ], components: [ UsersService ], exports: [ UsersService ], }) export class UsersModule { configure(consumer: MiddlewaresConsumer) { consumer.apply(AuthMiddleware).forRoutes(UsersController); } }
如上所示,模塊能夠有其餘方法,configure()
。此方法接收 MiddlewaresConsumer
對象做爲參數,它能夠幫助咱們配置中間件。
這個對象有 apply()
方法,它接收到無數的中間件(這個方法使用擴展運算符,因此能夠傳遞多個由逗號分隔的類)。 apply()
方法返回對象,它有兩種方法:
forRoutes()
:咱們使用這種方法經過逗號分隔控制器或對象(具備 path
和 method
屬性),不限個數,
with()
:咱們使用這種方法將自定義參數傳遞給 resolve()
中間件方法。
當你在 forRoutes
方法中傳遞 UsersController
時,Nest 將爲控制器中的每一個路由設置中間件:
GET: users GET: users/:id POST: users
可是也能夠直接定義應該使用哪一個路徑的中間件,就像這樣:
consumer.apply(AuthMiddleware).forRoutes({ path: '*', method: RequestMethod.ALL });
你能夠簡單的調用 apply()
鏈。
consumer.apply(AuthMiddleware, PassportMidleware) .forRoutes(UsersController, OrdersController, ClientController); .apply(...) .forRoutes(...);
中間件按照與數組相同的順序調用。在子模塊中配置的中間件將在父模塊配置以後被調用。
Nest 中有特殊組件稱爲網關。網關幫助咱們建立實時的網絡應用程序。他們是一些封裝的 socket.io
功能調整到框架架構。
網關是一個組件,所以它能夠經過構造函數注入依賴關係。網關也能夠注入到另外一個組件。
import { WebSocketGateway } from '@nestjs/websockets'; @WebSocketGateway() export class UsersGateway {}
默認狀況下,服務器在 80 端口上運行,並使用默認的命名空間。咱們能夠輕鬆地改變這些設置:
@WebSocketGateway({ port: 81, namespace: 'users' })
固然,只有當 UsersGateway
在 components
模塊組件數組中時,服務器纔會運行,因此咱們必須把它放在那裏:
@Module({ controllers: [ UsersController ], components: [ UsersService, UsersGateway ], exports: [ UsersService ], })
默認狀況下,網關有三個有用的事件:
afterInit
,做爲本機服務器 socket.io
對象參數,
handleConnection
和 handleDisconnect
,做爲本機客戶端 socket.io
對象參數。
有特殊的接口,OnGatewayInit
, OnGatewayConnection
和 OnGatewayDisconnect
來幫助咱們管理生命週期。
在網關中,咱們能夠簡單地訂閱發出的消息:
import { WebSocketGateway, SubscribeMessage } from '@nestjs/websockets'; @WebSocketGateway({ port: 81, namespace: 'users' }) export class UsersGateway { @SubscribeMessage('drop') handleDropMessage(sender, data) { // sender is a native socket.io client object } }
而在客戶端接收以下:
import * as io from 'socket.io-client'; const socket = io('http://URL:PORT/'); socket.emit('drop', { msg: 'test' });
@WebSocketServer()
若是要分配選定的 socket.io
本地服務器實例屬性,你可使用 @WebSocketServer()
裝飾器來簡單地進行裝飾。
import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; @WebSocketGateway({ port: 81, namespace: 'users' }) export class UsersGateway { @WebSocketServer() server; @SubscribeMessage('drop') handleDropMessage(sender, data) { // sender is a native socket.io client object } }
服務器初始化後將分配值。
網關中間件與路由器中間件幾乎相同。中間件是一個函數,它在網關消息用戶以前被調用。網關中間件函數能夠訪問本地 socket
對象。他們就像屏障同樣,若是中間件函數不調用 next()
,消息將永遠不會由用戶處理。
例如:
@Middleware() export class AuthMiddleware implements GatewayMiddleware { public resolve(): (socket, next) => void { return (socket, next) => { console.log('Authorization...'); next(); }; } }
關於網關中間件的一些事實:
你應該使用 @Middleware()
註解來告訴 Nest,這個類是一箇中間件,
你可使用 GatewayMiddleware
界面,這迫使你實現 resolve()
方法,
中間件(和組件同樣)能夠經過其構造函數注入依賴項(依賴關係必須是模塊的一部分),
中間件必須是 resolve()
方法,它必須返回另外一個函數(高階函數)
好了,咱們已經準備好中間件,可是咱們並無在任何地方使用它。咱們來設定一下:
@WebSocketGateway({ port: 2000, middlewares: [ ChatMiddleware ], }) export class ChatGateway {}
如上所示,@WebSocketGateway()
接受額外的元數組屬性 - middlewares
,它是一箇中間件數組。這些中間件將在消息處理程序前調用。
Nest 網關是一個簡單的組件,能夠注入到另外一個組件中。這個功能使得咱們有可能選擇將如何對消息作出反應。
固然,只有有必要,咱們能夠向網關注入組件並調用其適當的方法。
可是還有另外一個解決方案,網關反應流(Gateway Reactive Streams)。你能夠在這裏閱讀更多關於他們的信息。
將 Nest 應用程序轉換爲 Nest 微服務是很是簡單的。
讓咱們來看看如何建立 Web 應用程序:
const app = NestFactory.create(ApplicationModule); app.listen(3000, () => console.log('Application is listening on port 3000'));
如今,切換到微服務:
const app = NestFactory.createMicroservice(ApplicationModule, { port: 3000 }); app.listen() => console.log('Microservice is listening on port 3000'));
就是這樣!
默認狀況下, Nest 微服務經過 TCP 協議監聽消息。這意味着如今 @RequestMapping()
(以及 @Post()
, @Get()
等等)也不會有用,由於它是映射 HTTP 請求。那麼微服務如何識別消息?只是經過模式。
什麼是模式?沒什麼特別的,它是一個對象,字符串或者數字(但這不是一個好注意)。
讓咱們建立 MathController
:
import { MessagePattern } from '@nestjs/microservices'; @Controller() export class MathController { @MessagePattern({ cmd: 'add' }) add(data, respond) { const numbers = data || []; respond(null, numbers.reduce((a, b) => a + b)); } }
你可能會看到,若是你想建立消息處理程序,你必須使用@MessagePattern(pattern)
進行裝飾。在這個例子中,我選擇 { cmd: 'add' }
做爲模式。
該處理程序方法接收兩個參數:
data
,它是從另外一個微服務器(或者只是 Web 應用程序)發送的數據變量,
respond
,接收兩個參數(error
和 response
)的函數。
你已經知道了如何接收消息。如今,咱們來看看如何從另外一個微服務器或者 Web 應用程序發送它們。
在你開始以前,Nest 必須知道你要發送的郵件的位置。很簡單,你只須要建立一個 @Client
對象。
import { Controller } from '@nestjs/common'; import { Client, ClientProxy, Transport } from '@nestjs/microservices'; @Controller() export class ClientController { @Client({ transport: Transport.TCP, port: 5667 }) client: ClientProxy; }
@Client()
裝飾器接收對象做爲參數。此對象能夠有 3 個屬性:
transport
,經過這種方式,你能夠決定使用哪一種方法,TCP 或者 Redis(默認爲 TCP)
url
,僅用於 Redis 參數(默認爲 redis://localhost:6379
),
port
,端口,默認爲 3000。
讓咱們來建立自定義路徑來測試咱們的通訊。
import { Controller, Get } from '@nestjs/common'; import { Client, ClientProxy, Transport } from '@nestjs/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 })); } }
正如你看到的,爲了發送消息,你必須使用 send
方法,它接收消息模式和數據做爲參數。此方法從 Rxjs
包返回一個 Observable
。
這是很是重要的特性,由於 Observables
提供了一組使人驚奇的操做來處理,例如combine
, zip
, retryWhen
, timeout
等等。
固然,若是你想使用 Promise
而不是 Observables
,你能夠直接使用 toPromise()
方法。
就這樣。
如今,當有人發送 /test
請求(GET
)時,應該如何應用(若是微服務和 web 應用均可用):
{ "result": 15 }
還有另外一種方式與 Nest 微服務合做。咱們可使用 Redis
的發佈/訂閱功能,而不是直接 TCP 通訊。
在使用以前,你必須安裝 Redis。
要建立 Redis 微服務,你必須在 NestFactory.createMicroservice()
方法中傳遞其餘配置。
const app = NestFactory.createMicroservice( MicroserviceModule, { transport: Transport.REDIS, url: 'redis://localhost:6379' } ); app.listen(() => console.log('Microservice listen on port:', 5667 ));
就這樣。如今,你的微服務將訂閱經過 Redis 發佈的消息。其它方式依舊相同,如 模式和錯誤處理等等。
如今讓咱們來看看如何建立客戶端。之前,你的客戶端配置以下:
@Client({ transport: Transport.TCP, port: 5667 }) client: ClientProxy;
咱們想使用 Redis 而不是 TCP, 因此咱們必須改變這些配置:
@Client({ transport: Transport.REDIS, url: 'redis://localhost:6379' }) client: ClientProxy;
很容易,對麼? 就是這樣。其餘功能與 TCP 通訊中的功能相同。
Nest 模塊能夠導出其它組件。這意味着,咱們能夠輕鬆地在它們之間共享組件實例。
在兩個或者更多模塊之間共享實例的最佳方式是建立共享模塊。
例如,若是咱們要在整個應用程序中共享 ChatGateway
組件,咱們能夠這樣作:
import { Module, Shared } from '@nestjs/common'; @Shared() @Module({ components: [ ChatGateway ], exports: [ ChatGateway ] }) export class SharedModule {}
而後,咱們只須要將這個模塊導入到另外一個模塊中,這個模塊應該共享組件實例:
@Module({ modules: [ SharedModule ] }) export class FeatureModule {}
就這樣。
注意,也能夠直接定義共享模塊的範圍,瞭解更多細節。
依賴注入是一個強大的機制,能夠幫助咱們輕鬆地管理咱們類的依賴。它是很是受歡迎的語言,如 C# 和 Java。
在 Node.js 中,這些功能並不重要,由於咱們已經有了神奇的模塊加載系統,如在文件之間共享實例的很容易的。
模塊加載系統對於中小應用足夠用了。當代碼增加時,順利組織層之間的依賴就變得更加困難。總有一天會變得爆炸。
它比 DI 構造函數更直觀。這就是爲何 Nest 有本身的 DI 系統。
你已經瞭解到了,將組件添加到所選的模塊是很是容易的。
@Module({ controllers: [ UsersController ], components: [ UsersService ] })
是還有一些其餘狀況, Nest 容許你利用。
const value = {}; @Module({ controllers: [ UsersController ], components: [ { provide: UsersService, useValue: value } ], })
當:
你想要使用具體的值,如今,在這個模式中, Nest 將把 value
與 UsersService
元類型相關聯,
你想要使用單元測試。
@Component() class CustomUsersService {} @Module({ controllers: [ UsersController ], components: [ { provide: UsersService, useClass: CustomUsersService } ], })
當:
你只想在此模塊中使用所選的更具體的類。
@Module({ controllers: [ UsersController ], components: [ ChatService, { provide: UsersService, useFactory: (chatService) => { return Observable.of('value'); }, inject: [ ChatService ] } ], })
當:
你想提供一個值,它必須使用其餘組件(或定製包特性)來計算,
你要提供異步值(只返回 Observable
或 Promise
),例如數據庫鏈接。
請記住:
若是要使用模塊中的組件,則必須將它們傳遞給注入數組。 Nest 將按照相同的順序傳遞做爲參數的實例。
@Module({ controllers: [ UsersController ], components: [ { provide: 'isProductionMode', useValue: false } ], })
當:
你想提供一個選擇的鍵值。
請注意:
可使用各類類型的 useValue
, useClass
和 useFactory
。
怎麼使用?
使用選擇的鍵注入自定義提供組件 /
值,你必須告訴 Nest,就像這樣:
import { Component, Inject } from '@nestjs/common'; @Component() class SampleComponent { constructor(@Inject('isProductionMode') isProductionMode: boolean) { console.log(isProductionMode); // false } }
`
有時候你可能但願直接從模塊引用獲取組件實例。對於 Nest 並非一件大事,你只須要在類中注入 ModuleRef
:
export class UsersController { constructor( private usersService: UsersService, private moduleRef: ModuleRef) {} }
ModuleRef
提供一個方法:
get<T>(key)
,它返回等效鍵值實例(主要是元類型)。如 moduleRef.get<UsersService>(UsersService)
從當前模塊返回 UsersService
組件的實例
例如:
moduleRef.get<UsersService>(UsersService)
它將從當前模塊返回 UsersService
組件的實例。
Nest 爲你提供了一套測試工具,能夠提供應用測試過程。能夠有兩種方法來測試你的組件和控制器,隔離測試和使用專用的 Nest 測試工具。
Nest 的控制器和組件都是一個簡單的 JavaScript 類。這意味着,你能夠很容易的本身建立它們:
const instance = new SimpleComponent();
若是你的類還有其它依賴,你可使用 test doubles
,例如 Jasmine 或 Sinon 庫:
const stub = sinon.createStubInstance(DependencyComponent); const instance = new SimpleComponent(stub);
測試應用程序構建塊的另外一種方法是使用專用的 Nest 測試工具。
這些測試工具放在靜態 Test
類中(@nestjs/testing
模塊)。
import { Test } from '@nestjs/testing';
該類提供兩種方法:
createTestingModule(metadata: ModuleMetadata)
,它做爲參數接收簡單的模塊元數據(和 Module() class
相同)。此方法建立一個測試模塊(與實際 Nest 應用程序相同)並將其存儲在內存中。
get<T>(metatype: Metatype<T>)
,它返回選擇的實例(metatype
做爲參數傳遞),控制器/組件(若是它不是模塊的一部分,則返回 null
)。
例如:
Test.createTestingModule({ controllers: [ UsersController ], components: [ UsersService ] }); const usersService = Test.get<UsersService>(UsersService);
`
有時候你可能不想使用組件/控制器的實例。你能夠選擇這樣,使用 test doubles
或者 自定義 values / objects
。
const mock = {}; Test.createTestingModule({ controllers: [ UsersController ], components: [ { provide: UsersService, useValue: mock } ] }); const usersService = Test.get<UsersService>(UsersService); // mock
使用 Nest 能夠將異常處理邏輯移動到稱爲 Exception Filters
的特殊類。
讓咱們來看下下面的代碼:
@Get('/:id') public async getUser(@Response() res, @Param('id') id) { const user = await this.usersService.getUser(id); res.status(HttpStatus.OK).json(user); }
想象一下,usersService.getUser(id)
方法會拋出一個 UserNotFoundException
異常。咱們須要在路由處理程序中捕獲異常:
@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(); } }
總而言之,咱們必須向每一個可能發生異常的路由處理添加 try ... catch
塊。還有其它方式麼? 是的,Exception Filters
。
讓咱們建立 NotFoundExceptionFilter
:
import { Catch, ExceptionFilter, HttpStatus } from '@nestjs/common'; export class UserNotFoundException {} export class OrderNotFoundException {} @Catch(UserNotFoundException, OrderNotFoundException) export class NotFoundExceptionFilter implements ExceptionFilter { catch(exception, response) { response.status(HttpStatus.NOT_FOUND).send(); } }
`
如今,咱們只須要告訴咱們的 Controller
使用這個過濾器:
import { ExceptionFilters } from '@nestjs/common'; @ExceptionFilters(NotFoundExceptionFilter) export class UsersController {}
因此若是 usersService.getUser(id)
會拋出 UserNotFoundException
,那麼, NotFoundExceptionFilter
將會捕獲它。
每一個控制器可能具備無限次的異常過濾器(僅用逗號分隔)。
@ExceptionFilters(NotFoundExceptionFilter, AnotherExceptionFilter)
Exception filters
與組件相同,所以能夠經過構造函數注入依賴關係。
注意:它主要用於 REST 應用程序。
Nest 具備錯誤處理層,捕獲全部未處理的異常。
若是在某處,在你的應用程序中,你將拋出一個異常,這不是 HttpException
(或繼承的),Nest 會處理它並返回到下面用戶的 json 對象(500 狀態碼):
{ "message": "Unkown exception" }
在應用程序中,你應該建立本身的異常層次結構(Exceptions Hierarchy
)。全部的 HTTP 異常都應該繼承自內置的 HttpException
。
例如,您能夠建立 NotFoundException
和 UserNotFoundException
類:
import { HttpException } from '@nestjs/core'; export class NotFoundException extends HttpException { constructor(msg: string | object) { super(msg, 404); } } export class UserNotFoundException extends NotFoundException { constructor() { super('User not found.'); } }
而後,若是你的應用程序中的某個地方拋出 UserNotFoundException
,Nest 將響應用戶狀態代碼 404 及如下 json
對象:
{ "message": "User not found." }
它容許你專一於邏輯,並使你的代碼更容易閱讀。