公司的項目使用了大前端技術,大前端做爲中間層,整合了服務端的接口,也縮短了前端的開發等待接口的空窗期。全部的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);
}
複製代碼