内容简介:【译】基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(下)
本文为译文,原文地址: https://kamilmysliwiec.com/nest-final-release-is-here-node-js-framework-built-top-of-typescript ,作者,@Kamil Myśliwiec
上篇文章地址为: https://segmentfault.com/a/1190000009560532
中间件(Middlewares)
中间件是一个在路由处理程序前被调用的函数。中间件函数可以访问请求和响应对象,因此可以修改它们。它们也可以向一个 屏障 ,如果中间件函数不调用 next()
,请求将永远不会被路由处理程序处理。
让我们来构建一个虚拟授权中间件。我们将使用 X-Access-Token
HTTP 头来提供用户名(这是个奇怪的想法,但是不重要)。
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(); } } }
一些 事实 如下:
-
你应该使用
@Middleware()
注解来告诉 Nest,这个类是一个中间件, -
你可以使用
NestMiddleware
界面,这强制你使用resolve()
方法, -
中间件(与组件相同)可以通过其构造函数的 注入依赖项 (依赖关系必须是模块的一部分),
-
中间件必须有
resolve()
方法,它必须返回另一个函数(高阶函数)。为什么?因为有很多 第三方插件 准备使用express
中间件,你可以简单地在 Nest 中使用,还要感谢这个方案。
好了,我们已经准备好了中间件,但是我们并没有在任何地方使用它。我们来设置它:
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 });
链(Chaining)
你可以简单的调用 apply()
链。
consumer.apply(AuthMiddleware, PassportMidleware) .forRoutes(UsersController, OrdersController, ClientController); .apply(...) .forRoutes(...);
顺序
中间件按照与数组相同的顺序调用。在子模块中配置的中间件将在父模块配置之后被调用。
网关(Gateways)实时应用程序
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
,它是一个中间件数组。这些中间件将在 消息处理程序前 调用。
反应流(Reactive Streams)
Nest 网关 是一个简单的组件,可以注入到另一个组件中。这个功能使得我们有可能选择将如何对消息做出反应。
当然,只有有必要,我们可以向网关注入组件并调用其适当的方法。
但是还有另一个解决方案, 网关反应流 (Gateway Reactive Streams)。你可以在 这里 阅读更多关于他们的信息。
微服务(Microservices)支持
将 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'));
就是这样!
通过 TCP 进行通信
默认情况下, 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 }
Redis
还有另一种方式与 Nest 微服务合作。我们可以使用 Redis
的 发布/订阅 功能,而不是直接 TCP 通信。
在使用之前,你必须安装 Redis。
创建 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 发布的消息。其它方式依旧相同,如 模式和错误处理等等。
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 允许你利用。
使用 value :
const value = {}; @Module({ controllers: [ UsersController ], components: [ { provide: UsersService, useValue: value } ], })
当:
-
你想要使用具体的值,现在,在这个模式中, Nest 将把
value
与UsersService
元类型相关联, -
你想要使用单元测试。
使用 class :
@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 将按照相同的顺序传递作为参数的实例。
定制 providers
@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 } }
`
ModuleRef
有时候你可能希望直接从 模块引用 获取组件实例。对于 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 测试工具
测试应用程序构建块的另一种方法是使用专用的 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);
`
Mocks, spies, stubs
有时候你可能不想使用组件/控制器的实例。你可以选择这样,使用 test doubles
或者 自定义 values / objects
。
const mock = {}; Test.createTestingModule({ controllers: [ UsersController ], components: [ { provide: UsersService, useValue: mock } ] }); const usersService = Test.get<UsersService>(UsersService); // mock
异常过滤器(Exception Filters)
使用 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
与组件相同,因此可以通过构造函数注入依赖关系。
HttpException
注意:它主要用于 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." }
它允许你专注于逻辑,并使你的代码更容易阅读。
有用的参考
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- PHP 框架 CodeIgniter 4.0 正式版发布了!
- [译] PHP 框架 CodeIgniter 4 正式版发布了
- Angular 4.2.0 正式版发布,Web 前端框架
- QMUI Android UI 框架发布 1.0 正式版
- Angular 5.1.0 正式版发布,Web 前端框架
- WebMIS 1.0.0 正式版,全栈开发基础框架
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing for Emotion
Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00
Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!