公司的项目使用了大前端技术,大前端做为中间层,整合了服务端的接口,也缩短了前端的开发等待接口的空窗期。全部的mock数据在node层生成供调试,使前端开发造成了闭环。而node层所运用的框架中,nest是一个很是高效、好用的框架。javascript
Express
(默认),和Fastify
(可配置)。$ npm i -g @nestjs/cli
$ nest new project-name
复制代码
or前端
$ git clone https://github.com/nestjs/typescript-starter.git project $ cd project $ npm install $ npm run start 复制代码
$ npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata
复制代码
@Controller
,下例规定了路由前缀cat
传入控制器。如此一来咱们能够轻松地将一系列路由归为一组,减小了重复代码。后面全部路由都会在/cats
之下。import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll(): string { return 'This action returns all cats'; } } 复制代码
$ nest g controller cats
@Get()
告诉Nest须要为HTTP的get请求建立一个处理函数。standard(标准的) | 此配置使用内置的方法,当函数返回了一个对象或数组会自动转为JSON 格式,而返回字符时不会转换。这使得返回值处理十分简单,而Nest只关注剩余的部分。 |
Library-specific(规定库的) | 使用此配置能够经过在函数签名中注入@Res()装饰器来获取库的(Express)request对象(findAll(@Res() response) ),这样也就可使用原生的request对象暴露的API进行处理。例如response.status(200).send() |
不能够同时使用上述两种方法,当Nest监测到你使用了@Res()或者@Next()时,那么代表你选择了Library-specific项。二者同时使用时Nest会自动禁用standard项,而形成不可预期的后果。 java
@Req()
装饰器import { Controller, Get, Req } from '@nestjs/common'; import { Request } from 'express'; @Controller('cats') export class CatsController { @Get() findAll(@Req() request: Request): string { return 'This action returns all cats'; } } 复制代码
ps: 为了使用Express的类型能够安装`@types/express`包
复制代码
@Body()
或@Query()
。@Request() | req |
@Response() | res* |
@Next() | next |
@Session() | req.session |
@Param(key?: string) | req.params / req.params[key] |
@Body(key?: string) | req.body / req.body[key] |
@Query(key?: string) | req.query / req.query[key] |
@Headers(name?: string) | req.headers / req.headers[name] |
* 因为Library-specific的存在,会有两种风格的Resquest对象。standard模式使用@Request()
装饰器,而底层库的request对象使用@Req()
装饰器获取,并确保理解他们的不一样。node
import { Controller, Get, Post } from '@nestjs/common'; @Controller('cats') export class CatsController { @Post() create(): string { return 'This action adds a new cat'; } @Get() findAll(): string { return 'This action returns all cats'; } } 复制代码
@Put()
、@Delete()
、@Patch()
、@Options()
、@Head()
、 @All()
*
号通配符能够在路由中使用,能够匹配任意字符的结合@Get('ab*cd') findAll() { return 'This route uses a wildcard'; } 复制代码
abcd
, ab_cd
, abecd
等等?
、+
、*
、()
都可使用The hyphen ( -) and the dot (.) are interpreted literally by string-based paths.git
@HttpCode(...)
指定状态码@Post() @HttpCode(204) create() { return 'This action adds a new cat'; } 复制代码
@Header()
或者Library-specific模式的res对象@Post() @Header('Cache-Control', 'none') create() { return 'This action adds a new cat'; } 复制代码
@Param()
来获取动态路由参数@Get(':id') findOne(@Param() params): string { console.log(params.id); return `This action returns a #${params.id} cat`; } 复制代码
@Get(':id') findOne(@Param('id') id): string { return `This action returns a #${id} cat`; } 复制代码
@Controller('cats') export class CatsController { @Get(':id') findOne(@Param('id') id: string) { return `This action returns a #${id} cat`; } @Get() findAll() { // 此节点将不会命中,由于/cats请求将被/cats:id请求所截获 } } 复制代码
@Get() async findAll(): Promise<any[]> { return []; } 复制代码
export class CreateCatDto { readonly name: string; readonly age: number; readonly breed: string; } 复制代码
@Post() async create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; } 复制代码
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common'; import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto'; @Controller('cats') export class CatsController { @Post() create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; } @Get() findAll(@Query() query: ListAllEntities) { return `This action returns all cats (limit: ${query.limit} items)`; } @Get(':id') findOne(@Param('id') id: string) { return `This action returns a #${id} cat`; } @Put(':id') update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) { return `This action updates a #${id} cat`; } @Delete(':id') remove(@Param('id') id: string) { return `This action removes a #${id} cat`; } } 复制代码
@Module()
中包含controllers
数组import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; @Module({ controllers: [CatsController], // 数组 }) export class AppModule {} 复制代码
Provider
(依赖)Provider
是一个简单的使用了装饰器@Injectable()
的类。CatController
。控制器应只处理HTTP请求,而将其余复杂的业务交给供应者来处理。 SOLID
原则。(面向对象设计的五大原则)import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @Injectable() export class CatsService { private readonly cats: Cat[] = []; create(cat: Cat) { this.cats.push(cat); } findAll(): Cat[] { return this.cats; } } 复制代码
ps: 可使用脚手架命令nest g service cat
来建立服务。github
@Injectable()
。附带了元数据来告诉Nest这个类是一个Nest的提供者。顺带一提,此例也使用了一个Cat接口,也许长这样。export interface Cat { name: string; age: number; breed: string; } 复制代码
import { Controller, Get, Post, Body } from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; import { CatsService } from './cats.service'; import { Cat } from './interfaces/cat.interface'; @Controller('cats') export class CatsController { constructor(private readonly catsService: CatsService) {} @Post() async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } @Get() async findAll(): Promise<Cat[]> { return this.catsService.findAll(); } } 复制代码
private readonly
语法,这种表达式能够在马上这个位置同时声明并初始化成员(TS语法糖)。constructor(private readonly catsService: CatsService) {} 复制代码
Provider
的生命周期(做用域)与应用的声明周期同步。当应用建立时,全部的依赖必须被解析,所以每一个提供者也必须被实例化。同理,当应用关闭时,每一个提供者将会销毁。固然你也可使你提供者的生命周期在你的所需范围内。点击这里来阅读此技巧@Injectable()
只是冰山一角,而且也不是定义供应者的惟一方式。实际上你可使用简单的值、类、甚至是同步或异步的工厂。点击这里阅读更多案例import { Injectable, Optional, Inject } from '@nestjs/common'; @Injectable() export class HttpService<T> { constructor( @Optional() @Inject('HTTP_OPTIONS') private readonly httpClient: T ) {} } 复制代码
HTTP_OPTIONS
标识。前面的例子显示了基于构造器的注入,它经过构造函数中的类指示依赖关系。点击这里阅读更多关于自定义提供者而相关标识Provider
是很是"单调乏味"的。为了不这种状况,你能够在属性层使用装饰器@Inject()
。import { Injectable, Inject } from '@nestjs/common'; @Injectable() export class HttpService<T> { @Inject('HTTP_OPTIONS') private readonly httpClient: T; } 复制代码
若是你的类中没有使用另外的提供者,你应该始终使用以构造器为基础的注入 正则表达式
@Module()
模块中加入你的服务CatService
到提供者Providers
数组。import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; import { CatsService } from './cats/cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class AppModule {} 复制代码
@Module()
的类,装饰器提供元数据给Nest来组织应用结构。@Module()
装饰一个对象,属性用来描述这个模块。供应者Provider |
供应者将会被Nest实例化,而且这些供应者至少在当前模块中是共享的 |
控制器Controller |
这个属性定义了在当前模块中哪些控制器将会被实例化 |
输入imports |
引入模块的列表,引入了其余模块导出的在此模块中须要使用的提供者 |
输出exports |
此模块所使用的提供者的子集,导出的提供者在导入此模块的其余模块中可用 |
Provider
,这意味着你若是想注入一个既不是当前模块一部分的提供者,也不是导入的模块所导出的提供者,是不可能的。这样一来,你就要考虑从一个模块中导入一些提供者,做为模块的公共接口或者API。(一个公共模块)CatsController
和服务CatsService
是应用的同一块区域,他们应该放入特征模块中。import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule {} 复制代码
ps: 可使用脚手架指令nest g module cats
来建立模块。算法
import { Module } from '@nestjs/common'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], }) export class AppModule {} 复制代码
CatService
服务的实例,咱们须要经过在模块中增长exports
属性来导出提供者CatService
。import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], exports: [CatsService] }) export class CatsModule {} 复制代码
CatModule
模块,就能共享同一个服务实例。@Module({ imports: [CommonModule], exports: [CommonModule], }) export class CoreModule {} 复制代码
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule { constructor(private readonly catsService: CatsService) {} } 复制代码
@Global()
import { Module, Global } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Global() @Module({ controllers: [CatsController], providers: [CatsService], exports: [CatsService], }) export class CatsModule {} 复制代码
@Global()
使该模块在全局做用域下。全局模块应只被注册一次在核心模块或者根模块当中。在上例中,其余模块使用服务CatService
时,不用再引入该模块。ps: 不要处处使用全局模块,他只是为了减小大量的必需引入。大多数状况下更好的方式是使用imports
。typescript
DatabaseModule
import { Module, DynamicModule } from '@nestjs/common'; import { createDatabaseProviders } from './database.providers'; import { Connection } from './connection.provider'; @Module({ providers: [Connection], }) export class DatabaseModule { static forRoot(entities = [], options?): DynamicModule { const providers = createDatabaseProviders(options, entities); return { module: DatabaseModule, providers: providers, exports: providers, }; } } 复制代码
ps:forRoot()方法可能会同步或异步地返回一个动态模块。(经过Promise数据库
entities
和options
对象,暴露了一个提供者provider的集合,好比库。记住动态模块扩展(而非重载)了基础模块的元数据。这就是从模块导出静态声明的提供者Connection
和动态配置库的提供者的方式。DatabaseModule
模块就能以以下的方式导出并配置。import { Module } from '@nestjs/common'; import { DatabaseModule } from './database/database.module'; import { User } from './users/entities/user.entity'; @Module({ imports: [DatabaseModule.forRoot([User])], }) export class AppModule {} 复制代码
import { Module } from '@nestjs/common'; import { DatabaseModule } from './database/database.module'; import { User } from './users/entities/user.entity'; @Module({ imports: [DatabaseModule.forRoot([User])], exports: [DatabaseModule], }) export class AppModule {} 复制代码
request
和response
对象还有管道函数next()
。中间件函数能够完成如下任务
request
、response
对象request-response
循环request-response
循环,即必须调用next函数将控制权交给下个中间件函数。不然请求会一直处于挂起状态你能够用任何一个函数或者使用了装饰器@Injectable
的类实现中间件。若是是类应当实现NestMiddleware
接口,而函数不须要任何特殊的需求。让咱们使用类来实现一个简单的中间件。
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: Function) { console.log('Request...'); next(); } } 复制代码
@Modules()
中没有中间件的位置,取而代之咱们使用模块类中的configure()
函数来设置他们。包含中间件的模块必须实现NestModule
的接口。让咱们在根模块上设置一个LoggerMiddleware
的中间件。import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); } } 复制代码
CatsController
中定义的路由/cats
的处理函数以前设置了中间件LoggerMiddleware
。 当咱们在配置中间件时,经过传入一个包含路由path
和request
方法的对象给forRoutes()
来将中间件函数限制为一个特定的路由。在下面的例子中,注意咱们引入了枚举类型RequestMethod
,断言请求方法的类型。import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes({ path: 'cats', method: RequestMethod.GET }); } } 复制代码
*
做为通配符能够匹配任意字符结合。forRoutes({ path: 'ab*cd', method: RequestMethod.ALL }); 复制代码
MiddlewareConsumer
是一个辅助类,它提供了一些内置的方法来管理中间件。他们均可以简单地使用用链式风格连接起来。forRoutes()
方法可使用单个字符串,多字符串,一个RouteInfo
对象,一个控制器Controller
的类,或者甚至是多个控制器的类。大多数状况下你可能会传入一个用逗号分割的控制器的列表。下面的例子是单个控制器。import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes(CatsController); } } 复制代码
ps:apply()
方法接受一个中间件,或者多个中间件参数。
exclude()
方法来排除某个特定的路由。这个方法接受一个一个或多个对象来匹配须要排除路由path
和方法。consumer .apply(LoggerMiddleware) .exclude( { path: 'cats', method: RequestMethod.GET }, { path: 'cats', method: RequestMethod.POST } ) .forRoutes(CatsController); 复制代码
LoggerMiddleware
会绑定在CatsController
中全部的路由,除了传入exclude()
方法的两个对象。请记住exclude()
方法在函数中间件中没有做用。此外,exclude方法不会排除那些来自更通用的路由。(好比说通配符)。若是你须要那种级别上的控制,你应当把你路由限制的逻辑直接放在中间件中。例如:接受路由请求而且有条件地应用中间件的逻辑。LoggerMiddleware
类仍是很是简单。他没有成员,没有额外的方法,没有依赖。为何咱们不定义一个简单的函数而不是一个类呢。让咱们将它从类中间件转换为函数中间件。export function logger(req, res, next) { console.log(`Request...`); next(); }; 复制代码
consumer
.apply(logger)
.forRoutes(CatsController);
复制代码
ps: 考虑只在不须要任何依赖的状况下使用函数中间件。
apply()
方法的调用。consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
复制代码
INestApplication
接口提供的use()
方法。const app = await NestFactory.create(AppModule); app.use(logger); // 就像Express await app.listen(3000); 复制代码
{ "statusCode": 500, "message": "Internal server error" } 复制代码
@nestjs/common
包暴露。CatsController
中,咱们有一个findAll()函数
(一个Get路由请求)。假设这个函数由于一些缘由抛出了一个异常。@Get() async findAll() { throw new HttpException('Forbidden', HttpStatus.FORBIDDEN); } 复制代码
ps: 咱们这里使用Http状态码,是由@nestjs/common
导入的辅助枚举类型。
{ "statusCode": 403, "message": "Forbidden" } 复制代码
string | object
。传入一个字符来自定义错误信息。传入一个属性为status
和error
的字面量对象为第一个参数而不是字符,来彻底重载响应体。第二个参数应该是一个实际的Http状态码。@Get() async findAll() { throw new HttpException({ status: HttpStatus.FORBIDDEN, error: 'This is a custom message', }, 403); } 复制代码
而后响应像这样
{ "statusCode": 403, "error": "This is a custom message" } 复制代码
export class ForbiddenException extends HttpException { constructor() { super('Forbidden', HttpStatus.FORBIDDEN); } } 复制代码
ForbiddenException
继承自HttpException
,他将和内置的异常处理函数无缝衔接,因此咱们能够在findAll()
函数中使用@Get() async findAll() { throw new ForbiddenException(); } 复制代码
@nestjs/common
包暴露
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception.getStatus(); response .status(status) .json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, }); } } 复制代码
ps: 全部异常过滤器都必须实现ExceptionFilter<T>
接口。这须要你提供catch(exception: T, host: ArgumentsHost)
方法和签名。T
代表了异常的类型。
@Catch(HttpException)
装饰器为一场过滤器绑定了所需的元数据,告诉Nest这个过滤器正在寻找HttpException
类型的异常。@Catch()
装饰器接受一个参数,或者一个逗号分割的列表。这使你能够一次性设置多种不一样类型的异常。catch()
方法的参数,exception
参数是当前正在处理的异常对象,而host
是一个ArgumentsHost
对象。ArgumentsHost
是传递给原始请求处理函数的参数的包装器(异常初始化的地方),包含了一个基于应用类型的特定的数组。export interface ArgumentsHost { getArgs<T extends Array<any> = any[]>(): T; getArgByIndex<T = any>(index: number): T; switchToRpc(): RpcArgumentsHost; switchToHttp(): HttpArgumentsHost; switchToWs(): WsArgumentsHost; } 复制代码
ArgumentsHost
提供了一系列便利的方法来帮助咱们在不一样的应用环境下从基础数组中选择正确的参数,ArgumentsHost只不过是一组参数。举个例子,当过滤器使用的是Http应用环境,ArgumentsHost
将会提供一个[request, response]
数组。然而当当前环境是WebSocket应用环境,它包含一个[client, data]
数组。这种方法使你可以访问在自定义catch()方法中最终传递给原始处理函数的任何参数。HttpExceptionFilter
和CatsController
的create()
函数联系起来。@Post() @UseFilters(new HttpExceptionFilter()) async create(@Body() createCatDto: CreateCatDto) { throw new ForbiddenException(); } 复制代码
ps: 装饰器@UseFilters()
由@nestjs/common
包导出。
@UseFilters()
。相似于@Catch()
装饰器,它接受一个过滤器实例,过着一个逗号分割的过滤器实例的列表。这里咱们建立了HttpExceptionFilter
的实例。或者你也能够传入类(而不是实例),将实例化的责任交给框架,而且启用依赖注入。@Post() @UseFilters(HttpExceptionFilter) async create(@Body() createCatDto: CreateCatDto) { throw new ForbiddenException(); } 复制代码
ps: 尽量地使用类而不是实例来注册过滤器。它减小了内存使用,由于Nest在你的模块中能够轻易地重用同一个类的实例。
HttpExceptionFilter
仅应用于单个create()
路由处理函数,使其具备方法范围。异常过滤器能够在不一样的层肯定做用域:方法做用域、控制器做用域或全局做用域。例如,设置一个控制器做用域的过滤器:@UseFilters(new HttpExceptionFilter()) export class CatsController {} 复制代码
CatsController
中定义的每一个路由处理函数设置了HttpExceptionFilter
。async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(3000); } bootstrap(); 复制代码
`useGlobalFilters()`方法不为网关或混合应用程序设置过滤器。
useGlobalFilters()
)不能注入依赖项,由于这是在任何模块的环境以外完成的。你可使用如下构造直接从任何模块注册一个全局范围的过滤器import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; @Module({ providers: [ { provide: APP_FILTER, useClass: HttpExceptionFilter, }, ], }) export class AppModule {} 复制代码
ps: 当使用此方法为过滤器执行依赖项注入时,请注意,不管使用此构造的模块是什么,过滤器实际上都是全局的。这应该在哪里进行?选择定义过滤器(上面示例中的HttpExceptionFilter)地方的模块。而且,useClass
不是处理自定义提供者注册的惟一方法。
@Catch()
传空就能够了。import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, }); } } 复制代码
BaseExceptionFilter
并调用继承的catch()
方法。import { Catch, ArgumentsHost } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; @Catch() export class AllExceptionsFilter extends BaseExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { super.catch(exception, host); } } 复制代码
扩展`BaseExceptionFilter`的方法范围和控制范围的过滤器不该该用new实例化,让框架自动实例化它们。
HttpServer
参数async function bootstrap() { const app = await NestFactory.create(AppModule); const { httpAdapter } = app.get(HttpAdapterHost); app.useGlobalFilters(new AllExceptionsFilter(httpAdapter)); await app.listen(3000); } bootstrap(); 复制代码
APP_FILTER
标识像这样@Injectable()
的类,管道应该实现PipeTransform
接口ps:管道运行在异常区域内。这意味这当管道抛出异常时,由异常层(全局异常过滤器和应用于当前环境的任何异常过滤器)处理。根据上面的说明,当异常在管道中抛出时,显然不会执行控制器方法。
ValidationPipe
、ParseIntPipe
、ParseUUIDPipe
。他们从@nestjs/common
导出,为了更好地理解它们是如何工做的,让咱们从头开始构建它们。ValidationPipe
开始。起先咱们简单地传入一个值并马上返回相同的值,就像一个确认函数。import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ValidationPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { return value; } } 复制代码
ps: PipeTransform<T, R>
是一个泛型接口,其中T
表示输入值的类型,R
表示transform()
方法的返回类型。
tranform()
方法,由两个参数:
export interface ArgumentMetadata { readonly type: 'body' | 'query' | 'param' | 'custom'; readonly metatype?: Type<any>; readonly data?: string; } 复制代码
type | 代表了参数是@body() 、@Query() 、@Param() 或者是一个自定义参数 |
metatype | 为参数提供了元数据,例如字符。注意:若是在路由处理函数的方法签名中省略类型声明,或者使用普通JavaScript,则该值为undefined 。 |
data | 传入装饰器的字符。例如:@Body('string') ,传空即为undefined |
TypeScript的接口(Interface)在转换期间会消失。所以,若是方法参数的类型声明为接口而不是类,则metatype值将为Object。
CatsController
的create()
方法。@Post() async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 复制代码
createCatDto
的body
参数。它的类型是CreateCatDto
export class CreateCatDto { readonly name: string; readonly age: number; readonly breed: string; } 复制代码
create
方法来讲任何传入的请求都包含一个有效的请求体。所以,咱们必须验证createCatDto
对象的三个成员。咱们固然能够在路由处理函数中作这件事情,但咱们将会打破单一责任规则(SRP)。另外一种方法是建立一个验证器类并在其中委托任务,可是咱们必须在每一个方法的开头使用这个验证器。那建立一个验证中间件怎么样?这多是一个好主意,可是这没有办法建立能够跨整个应用程序使用的通用的中间件(由于中间件不知道执行环境,包括将要调用的处理函数及其任何参数)。事实证实,这种状况很是适合于管道。因此咱们来作一个。$ npm install --save @hapi/joi
$ npm install --save-dev @types/hapi__joi
复制代码
在下面的代码示例中,咱们建立了一个简单的类,它接受模式做为构造函数参数。而后咱们应用jo .validate()
方法,该方法根据提供的模式验证传入参数。
如上所述,验证的管道要么返回未更改的值,要么抛出异常。
在下一节中,你将看到咱们如何使用@UsePipes()
装饰器为给定的控制器方法提供适当的模式。
import * as Joi from '@hapi/joi'; import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; @Injectable() export class JoiValidationPipe implements PipeTransform { constructor(private readonly schema: Object) {} transform(value: any, metadata: ArgumentMetadata) { const { error } = Joi.validate(value, this.schema); if (error) { throw new BadRequestException('Validation failed'); } return value; } } 复制代码
@UsePipes()
装饰器并建立一个管道实例,将一个Joi验证模式传递给它。@Post() @UsePipes(new JoiValidationPipe(createCatSchema)) async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 复制代码
本节中的技术须要TypeScript,若是应用程序是使用普通JavaScript编写的,则不可用
$ npm i --save class-validator class-transformer
复制代码
CreateCatDto
类添加一些装饰器。import { IsString, IsInt } from 'class-validator'; export class CreateCatDto { @IsString() readonly name: string; @IsInt() readonly age: number; @IsString() readonly breed: string; } 复制代码
ps:更多关于类验证器请阅读这里
ValidationPipe
类import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; @Injectable() export class ValidationPipe implements PipeTransform<any> { async transform(value: any, { metatype }: ArgumentMetadata) { if (!metatype || !this.toValidate(metatype)) { return value; } const object = plainToClass(metatype, value); const errors = await validate(object); if (errors.length > 0) { throw new BadRequestException('Validation failed'); } return value; } private toValidate(metatype: Function): boolean { const types: Function[] = [String, Boolean, Number, Array, Object]; return !types.includes(metatype); } } 复制代码
ps: 上面,咱们使用了类转换器库。它是由同一个做者编写的,与类验证器库是同一个做者编写的,所以,它们在一块儿运行得很是好。
transform()
函数是异步的。这是可能的,由于Nest同时支持同步和异步管道。咱们这样作是由于一些类验证器验证能够是异步的(利用promise)。metatype
字段(仅从ArgumentMetadata
中提取此成员)到metatype
参数中。这只是获取完整ArgumentMetadata
的简写,而后使用附加语句来分配元类型变量。toValidate()
。当当前处理的参数是原生JavaScript类型时,它负责绕过验证步骤(这些参数不能附加模式,所以没有理由在验证步骤中运行它们)。plain toclass()
将普通JavaScript参数对象转换为类型化对象,以便应用验证。从网络请求反序列化传入的主体时,会没有任何类型的信息。类验证器须要使用前面为DTO定义的验证装饰器,所以须要执行此转换。ValidationPipe
。管道相似于异常过滤器,能够是方法范围的、控制范围的或全局范围的。此外,管道能够是参数做用域的。在下面的示例中,咱们将直接将管道实例绑定到路由参数的@Body()
装饰器。@Post() async create( @Body(new ValidationPipe()) createCatDto: CreateCatDto, ) { this.catsService.create(createCatDto); } 复制代码
当验证逻辑只涉及一个指定参数时,参数做用域的管道很是有用。
或者,要用方法级别设置管道,可使用@UsePipes()
装饰器。
@Post() @UsePipes(new ValidationPipe()) async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 复制代码
@UesPipe()
装饰器由@nestjs/common
导出ValidationPipe
实例。或者,传递类(而不是实例),从而将实例化留给框架,并启用依赖项注入。@Post() @UsePipes(ValidationPipe) async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 复制代码
ValidationPipe
被建立为尽量通用,因此让咱们将它设置为全局范围的管道,应用于整个应用程序中的每一个路由处理程序。async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); 复制代码
ps: 在混合应用程序的状况下,useGlobalPipes()
方法不会为网关和微服务设置管道。对于“标准”(非混合)微服务应用程序,useGlobalPipes()
在全局安装管道。
useGlobalPipes()
)不能注入依赖项,由于这是在任何模块的环境以外完成的。为了解决这个问题,您可使用如下构造从任何模块直接设置全局管道import { Module } from '@nestjs/common'; import { APP_PIPE } from '@nestjs/core'; @Module({ providers: [ { provide: APP_PIPE, useClass: ValidationPipe, }, ], }) export class AppModule {} 复制代码
ps: 当使用此方法为管道执行依赖项注入时,请注意,不管使用此构造的模块是什么,管道实际上都是全局的。这应该在哪里进行?选择定义管道的模块(上面示例中的ValidationPipe
)。并且,useClass
不是处理自定义提供者注册的惟一方法。了解更多。
Transformer pipes
能够经过在客户端请求和请求处理程序之间插入一个处理函数来执行这些功能。ParseIntPipe
,它负责将字符串解析为整数值。import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; @Injectable() export class ParseIntPipe implements PipeTransform<string, number> { transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10); if (isNaN(val)) { throw new BadRequestException('Validation failed'); } return val; } } 复制代码
@Get(':id') async findOne(@Param('id', new ParseIntPipe()) id) { return await this.catsService.findOne(id); } 复制代码
ParseUUIDPipe
,它负责解析字符串并验证是否为UUID。@Get(':id') async findOne(@Param('id', new ParseUUIDPipe()) id) { return await this.catsService.findOne(id); } 复制代码
ps: 当使用ParseUUIDPipe()
时,您在版本三、4或5中解析UUID,若是您只须要特定版本的UUID,那么您能够在管道选项中传递一个版本。
ParseIntPipe
或ParseUUIDPipe
将在请求到达相应的处理函数以前执行,确保它老是会收到id参数的整数或uuid(根据使用的管道)。另外一种有用的状况是经过id从数据库中选择一个现有的用户实体@Get(':id') findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) { return userEntity; } 复制代码
UserEntity
对象)。经过将样板代码从处理函数抽象到公共管道中,这可使您的代码更具声明性和干爽性。ValidationPipe
和ParseIntPipe
是由Nest开箱即用提供的。(请记住ValidationPipe
须要同时安装类验证器和类转换器包)。ValidationPipe
提供了比咱们在本章中构建的示例更多的选项,为了说明管道的基本机制,本章保持了基本的验证。你能够在这里找到不少例子。class-transform
将普通对象临时转换为类型化对象,以便进行验证。内置的ValidationPipe
还能够选择性地返回这个转换后的对象。咱们经过向管道传递配置对象来启用此行为。对于这个选项,传递一个带有字段转换和值true的配置对象@Post() @UsePipes(new ValidationPipe({ transform: true })) async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 复制代码
ps: ValidationPipe
从@nestjs/common package
导出。
export interface ValidationPipeOptions extends ValidatorOptions { transform?: boolean; disableErrorMessages?: boolean; exceptionFactory?: (errors: ValidationError[]) => any; } 复制代码
ValidatorOptions
接口)都是可用的配置 | 类型 | 描述 |
---|---|---|
skipMissingProperties | boolean | 若是设置为true,验证器将跳过验证对象中缺乏的全部属性的验证。 |
whitelist | boolean | 若是设置为true,validator 将剥离不使用任何验证修饰符的任何属性的已验证(返回)对象。 |
forbidNonWhitelisted | boolean | 若是设置为true,则抛出异常而不是剥离非白名单属性验证器。 |
forbidUnknownValues | boolean | 若是设置为true,验证未知对象的尝试将当即失败。 |
disableErrorMessages | boolean | 若是设置为true,验证错误将不会返回给客户端。 |
exceptionFactory | Function | 获取验证错误数组,并返回要抛出的异常对象。 |
groups | string[] | 在对象验证期间使用的数组。 |
dismissDefaultMessages | boolean | 若是设置为true,验证将不使用默认信息。若是没有明确设置错误信息,则该信息始终是undefined 的 |
validationError.target | boolean | 表示目标是否应在ValidationError 中暴露 |
validationError.value | boolean | 表示验证的值是否应在ValidationError 中暴露 |
ps: 在其存储库中查找有关类验证器包的更多信息。
@Injectable()
的类,守卫应该继承CanActivate
接口request
对象上增长属性和一个特定的路由环境(还有他的元数据)并无很大的关联。next()
方法后哪个路由处理函数将被执行。另外一方面,守卫能够访问ExecutionContext
的实例,这样就能准确地知道下一个执行的函数。它们的设计很像异常过滤器、管道和拦截器,让你在request/response
循环中在正确的位置插入处理逻辑,并用声明的方式来实现。ps:守卫会在每一个中间件以后执行,可是会在拦截器和管道以前。
AuthGuard
假设有一个通过身份验证的用户(所以,请求头附加了一个令牌)。它将提取和验证令牌,并使用提取的信息来肯定请求是否能够继续。import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); return validateRequest(request); } } 复制代码
validateRequest()
函数中的逻辑能够根据须要简单或复杂。本例的主要目的是展现守卫是如何适应request/response
循环canActivate()
函数。函数应该返回一个布尔值,表示了当前请求是否容许经过。他能够同步或异步地返回响应(经过Promise或者Observable)。Nest根据返回值来控制下一个动做:
canActivate()
函数接受一个参数,ExecutionContext
的实例。ExecutionContext
继承自ArgumentsHost
。咱们在以前异常过滤器的章节中看到过ArgumentsHost
。在那里,它是传递给原始处理函数的参数的包装器,而且包含了基于应用类型的不一样的参数数组。有关此主题的更多信息,请参阅异常过滤器。ArgumentsHost
,ExecutionContext
提供了关于当前执行过程的更多细节。export interface ExecutionContext extends ArgumentsHost { getClass<T = any>(): Type<T>; getHandler(): Function; } 复制代码
getHandler()
方法返回对将要调用的处理函数的引用。getClass()
方法返回这个特定处理函数所属的控制器类的类型。例如,若是当前处理的请求是一个POST请求,目标是CatsController
上的create()
方法,getHandler()
将返回对create()
方法的引用,getClass()
将返回一个CatsController
类型(而不是实例)。import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class RolesGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { return true; } } 复制代码
@ useguard()
装饰器设置了一个控制器范围的守卫。这个装饰器可使用单个参数,也可使用逗号分隔的参数列表。‘@Controller('cats') @UseGuards(RolesGuard) export class CatsController {} 复制代码
ps: UseGuards()
装饰器由@nestjs/common
导出
RolesGuard
类型(而不是实例),将实例化的责任留给框架并启用依赖项注入。与管道和异常过滤器同样,咱们也能够就地传递一个实例。@Controller('cats') @UseGuards(new RolesGuard()) export class CatsController {} 复制代码
@useguard()
装饰器。useglobalguard()
方法const app = await NestFactory.create(AppModule); app.useGlobalGuards(new RolesGuard()); 复制代码
ps: 在混合应用程序的状况下,useglobalguard()
方法不会为网关和微服务设置保护。对于“标准”(非混合)微服务应用程序,useglobalguard()
确实在全球安装了这些警卫。
useglobalguard()
)不能注入依赖项,由于这是在任何模块的环境以外完成的。为了解决这个问题,您可使用如下构造从任何模块直接设置一个守卫import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; @Module({ providers: [ { provide: APP_GUARD, useClass: RolesGuard, }, ], }) export class AppModule {} 复制代码
ps: 当使用此方法为守卫执行依赖项注入时,请注意,不管使用此构造的模块是什么,守卫实际上都是全局的。这应该在哪里进行?选择定义守卫的模块(在上面的例子中是RolesGuard
)。并且,useClass
不是处理自定义提供者注册的惟一方法。了解更多。
RolesGuard
工做正常,但还不是很智能。咱们尚未利用最重要的守卫特性——执行环境。它还不知道角色,或者每一个处理程序容许哪些角色。例如,CatsController
能够为不一样的路由提供不一样的权限方案。其中一些可能只对管理员用户可用,而另外一些则能够对全部人开放。咱们如何以灵活和可重用的方式将角色匹配到路由?@SetMetadata()
装饰器将定制元数据附加到路由处理程序的能力。这个元数据提供了咱们丢失的角色数据,智能守卫须要这些数据来作出决策。让咱们看看如何使用@SetMetadata()
@Post() @SetMetadata('roles', ['admin']) async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 复制代码
ps: 装饰器@SetMetadata()
由nestjs/common
包导出
['admin']
是一个特定值)附加到create()
方法。虽然这样作是有效的,可是在路由中直接使用@SetMetadata()
不是很好的作法。相反,建立您本身的装饰器,以下所示import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles); 复制代码
@Roles()
装饰器,咱们可使用它来装饰create()
方法。@Post() @Roles('admin') async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 复制代码
RolesGuard
绑在一块儿。目前,它只是在全部状况下返回true,容许每一个请求继续。咱们但愿将分配给当前用户的角色与正在处理的当前路由所需的实际角色进行比较,从而使返回值具备条件。为了访问路由的角色(自定义元数据),咱们将使用Reflector
辅助类,它由框架提供,并从@nestjs/common
公开。import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const roles = this.reflector.get<string[]>('roles', context.getHandler()); if (!roles) { return true; } const request = context.switchToHttp().getRequest(); const user = request.user; const hasRole = () => user.roles.some((role) => roles.includes(role)); return user && user.roles && hasRole(); } } 复制代码
ps: 在node.js中,一般将受权用户附加到请求对象。所以,在上面的示例代码中,咱们假设该请求。user包含用户实例和容许的角色。在您的应用程序中,您可能会在自定义身份验证守卫(或中间件)中建立关联。
Reflector
类容许咱们经过指定的键轻松地访问元数据(在本例中,键是“roles”;返回到roles.decorator.ts
文件和在那里发出的SetMetadata()
调用。在上面的示例中,为了提取当前处理的请求方法的元数据,咱们传递了context.getHandler()
。记住,getHandler()
提供了对路由处理函数的引用。context.getClass()
而不是context.getHandler()
const roles = this.reflector.get<string[]>('roles', context.getClass()); 复制代码
{ "statusCode": 403, "message": "Forbidden resource" } 复制代码
注意,在后台,当一个守卫返回false时,框架抛出一个ForbiddenException
。若是但愿返回不一样的错误响应,应该抛出本身的特定异常。例如
throw new UnauthorizedException(); 复制代码
@injectable
的类,拦截器应该实现NestInterceptor
接口intercept()
方法,接受两个参数。第一个是ExecutionContext
的实例(和守卫同样)。ExecutionContext
继承自ArgumentsHost
。咱们在前面的异常过滤器一章中看到了ArgumentsHost
。在那里,咱们看到它是传递给原始处理函数的参数的包装器,并根据应用程序的类型包含不一样的参数数组。ArgumentsHost
, ExecutionContext
提供了关于当前执行过程的更多细节。这是它的样子export interface ExecutionContext extends ArgumentsHost { getClass<T = any>(): Type<T>; getHandler(): Function; } 复制代码
getHandler()
方法返回对将要调用的路由处理程序的引用,getClass()
方法返回这个特定处理程序所属的控制器类的类型。例如,若是当前处理的请求是一个POST请求,目标是CatsController
上的create()
方法,getHandler()
将返回对create()
方法的引用,getClass()
将返回一个CatsControllertype
(而不是实例)。CallHandler
。CallHandler
接口实现handle()
方法,你可使用该方法在拦截器中的某个点调用路由处理函数。若是在拦截器intercept()
方法的实现中不调用handle()
方法,则根本不会执行路由处理函数。import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { console.log('Before...'); const now = Date.now(); return next .handle() .pipe( tap(() => console.log(`After... ${Date.now() - now}ms`)), ); } } 复制代码
ps: NestInterceptor<T, R>
是一个泛型接口,其中T
表示一个Observable<T>
(支持响应流)的类型,R
是Observable<R>
封装的值的类型。
ps: 拦截器,如控制器、提供者、守卫等,能够经过它们的构造函数注入依赖项。
handle()
返回一个RxJs的Observable
,因此咱们可使用多种操做符来操做流。在上面的例子中,咱们使用了tap()
操做符,在observable流优雅或异常终止时调用匿名日志函数,但不会在其余方面干扰响应循环。@nestjs/common
导入的@UseInterceptors()
装饰器,与管道和守卫同样,拦截器能够是控制器范围的、方法范围的或全局范围的。@UseInterceptors(LoggingInterceptor) export class CatsController {} 复制代码
CatsController
中定义的每一个路由处理函数都将使用LoggingInterceptor
。当有人调用GET /cats
端点时,您将在标准输出中看到如下输出Before... After... 1ms 复制代码
LoggingInterceptor
类型(而不是实例),将实例化的责任留给框架并启用依赖项注入。与管道、守卫和异常过滤器同样,咱们也能够传递一个就地实例。@UseInterceptors(new LoggingInterceptor()) export class CatsController {} 复制代码
如上所述,上面的构造将拦截器附加到此控制器声明的每一个处理函数。若是咱们想将拦截器的范围限制为一个方法,咱们只需在方法级别应用装饰器。
为了设置全局拦截器,咱们使用了Nest应用程序实例的useGlobalInterceptors()
方法
const app = await NestFactory.create(AppModule); app.useGlobalInterceptors(new LoggingInterceptor()); 复制代码
useGlobalInterceptors()
,如上例所示)不能注入依赖项,由于这是在任何模块的环境以外完成的。为了解决这个问题,您可使用如下构造直接从任何模块设置拦截器import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor, }, ], }) export class AppModule {} 复制代码
ps: 当使用此方法为拦截器执行依赖项注入时,请注意,不管使用此构造的模块是什么,拦截器实际上都是全局的。这应该在哪里进行?选择定义拦截器(上例中的LoggingInterceptor
)的模块。并且,useClass
不是处理自定义提供者注册的惟一方法。了解更多。
handle()
返回一个Obseverable
对象。流包含路由处理函数返回的值,所以咱们可使用RxJS的map()操做符轻松地对其进行修改。响应映射特性与指定库library-specific的响应策略不兼容(禁止直接使用`@Res()`对象)。
TransformInterceptor
,它将以一种简单的方式修改每一个响应,以演示流程。它将使用RxJS的map()
操做符将响应对象分配给新建立对象的data
属性,并将新对象返回给客户端。import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface Response<T> { data: T; } @Injectable() export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> { return next.handle().pipe(map(data => ({ data }))); } } 复制代码
ps: 嵌套拦截器同时使用同步和异步intercept()
方法。若是须要,您能够简单地将方法切换到async
。
GET /cats
端点时,响应将以下所示(假设路由处理程序返回一个空数组[]){ "data": [] } 复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class ExcludeNullInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next .handle() .pipe(map(value => value === null ? '' : value )); } } 复制代码
catchError()
操做符覆盖抛出的异常import { Injectable, NestInterceptor, ExecutionContext, BadGatewayException, CallHandler, } from '@nestjs/common'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Injectable() export class ErrorsInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next .handle() .pipe( catchError(err => throwError(new BadGatewayException())), ); } } 复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, of } from 'rxjs'; @Injectable() export class CacheInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const isCached = true; if (isCached) { return of([]); } return next.handle(); } } 复制代码
isCached
变量和一个硬编码的响应数组。须要注意的关键点是,咱们在这里返回一个由RxJSof()
操做符建立的新流,所以根本不会调用路由处理程序。当有人调用使用CacheInterceptor
的端点时,响应(硬编码的空数组)将当即返回。为了建立一个通用的解决方案,您能够利用Reflector
并建立一个自定义装饰器。Reflector
在守卫一章中有很好的描述。import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { timeout } from 'rxjs/operators'; @Injectable() export class TimeoutInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe(timeout(5000)) } } 复制代码
decorator
的语言特性构建的。装饰器在许多经常使用的编程语言中都是一个众所周知的概念,但在JavaScript世界中,它们仍然相对较新。为了更好地理解decorator是如何工做的,咱们建议阅读本文。这里有一个简单的定义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.
@Request() | req |
@Response() | res* |
@Next() | next |
@Session() | req.session |
@Param(key?: string) | req.params / req.params[key] |
@Body(key?: string) | req.body / req.body[key] |
@Query(key?: string) | req.query / req.query[key] |
@Headers(name?: string) | req.headers / req.headers[name] |
const user = req.user; 复制代码
import { createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator((data, req) => { return req.user; }); 复制代码
sync findOne(@User() user: UserEntity) { console.log(user); } 复制代码
data
参数将参数传递给装饰器的工厂函数。一个用例是自定义装饰器,它按键从请求对象中提取属性。例如,假设咱们的身份验证层验证请求并将用户实体附加到请求对象。通过身份验证的请求的用户实体可能以下所示{ "id": 101, "firstName": "Alan", "lastName": "Turing", "email": "alan@email.com", "roles": ["admin"] } 复制代码
import { createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator((data: string, req) => { return data ? req.user && req.user[data] : req.user; }); 复制代码
@Get() async findOne(@User('firstName') firstName: string) { console.log(`Hello ${firstName}`); } 复制代码
@Body()
、@Param()
和@Query()
相同的方式处理定制的参数装饰器。这意味着管道也将为自定义带注释的参数执行(在咱们的示例中,是用户参数)。此外,您还能够将管道直接应用于自定义装饰器@Get() async findOne(@User(new ValidationPipe()) user: UserEntity) { console.log(user); } 复制代码