Nest
(NestJS
) 是一個用於搭建高性能、可伸縮的Node.js
服務端應用的框架。它使用漸進式的JavaScript
,內置且徹底支持Typescript
(但開發人員能夠使用純JavaScript
進行編程) , 同時結合了OOP
(面向對象編程),FP
(函數式編程)和FRP
(響應式編程)的原理。html
在傳統Node.js
服務端應用中,controller
用於處理route
對應的客戶端請求。每一個controller
能夠擁有多個route
,不一樣的route
會執行不一樣的action
(行爲)。在Nest
中,咱們能夠使用內置的decorators
來對request
, response
, controller
、route
、action
等元素進行定製化的修飾。從而抽離重複代碼,提升開發效率。以下所示:react
// 省略依賴代碼
@Controller('cats') // 模塊名稱,用於定義route前綴:/cats
export class CatsController {
// 修改該route的成功響應時的狀態碼爲204
@HttpCode(204)
// 修改該response的響應頭參數
@Header('Cache-Control', 'none')
// 修飾該route([POST] /cats)的請求方法:POST,@Get、@Post、@Delete同理
@Post()
// @Bady 修飾參數,此處將會傳入request.body,@Query()、@Param同理
create(@Body() createCatDto: CreateCatDto) {
// 返回響應的數據
return 'This action adds a new cat';
}
@Redirect("http://www.fzzf.top") // 路由重定向
@Get(":id") // [GET] /cats/:id
show(@Query("") query: ListAllEntities) {
// do something
}
}
複製代碼
對於controller
的返回值,Nest
支持基於async/await
返回的promise
,也支持基於RxJS
返回的Observable Streams
,示例以下:es6
import { of } from 'rxjs';
// controller代碼省略
@Put(":id")
async update(@Param() params: UpdateCatDto): Promise<any> {
return this.catsService.update(params);
}
// 注意不要加async
@Get()
index(@Query() query: IndexCatDto): Observable<any[]> {
return of(this.catsService.find(query));
}
複製代碼
在Nest
中,provider
被定義爲擁有特定功能方法集合的類。經過裝飾器@Injectable()
修飾後成爲能夠做爲依賴注入到Nest Module
的服務。處於相同的做用域的provider
之間只要,其中一個provider
也能夠做爲依賴注入到另外一個provider
中。以下代碼所示:express
// cats.service
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
constructor(
private readonly otherService: OtherService
)
async create(values: any): Promise<any> {
return this.otherService.create(values);
}
}
複製代碼
咱們能夠聲明一個module
(參考下一節),將provider
注入到controller
中。依賴注入的細節由Nest
框架內部完成,這裏只介紹基本的依賴注入姿式,以下面代碼所示:編程
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController], // 當前module的controller
providers: [CatsService], // 須要注入的provider
})
export class CatModule {}
// cats.controller
import { CatsService } from './cats/cats.service';
@Controller('cats')
export class CatsController {
constructor(
private readonly catsService: CatsService // 依賴注入
) { }
@Post()
create(@Body() createCatDto: CreateCatDto) {
// 返回響應的數據
return this.catsService.create(createCatDto);
}
}
複製代碼
module
是Nest
應用的基礎組成單位。一個Nest
應用至少擁有一個module(root module)
。簡單來講,在Nest
中,n個功能module
經過依賴注入組成一個業務module
,n個業務 module
組成一個Nest
應用程序。不管是controller
仍是provider
都須要經過module
註冊才能完成依賴的注入。下面的代碼介紹了基礎的module
聲明姿式。json
@Module({
// 依賴的其餘module類
imports: [],
// 業務模塊所需的controller類,它將以爲module如何是實例化爲業務模塊仍是功能模塊
controller: [],
// 提供依賴的類
provider: [],
// 導出模塊中provider,能夠被其餘模塊依賴注入
exports: [],
})
export class AppModule {}
複製代碼
這裏咱們能夠經過傳遞exports
參數,才完成module
之間層級依賴,以及不一樣module
中的provider
可以被複用。以下面代碼所示:api
// request.module.ts
@Module({
// 註冊provider
provider: [RequestService],
// 導出註冊的provider
exports: [RequestService],
})
export class RequestModule {}
// api.module.ts
@Module({
// 導入一個RequestModule,Nest會註冊其中export的provider
imports: [RequestModule],
// 註冊controller
controllers: [ApiController],
})
export class ApiModule {}
// api.controller.ts
@Controller('api')
export class ApiController {
constructor(
private readonly requestService: RequestService // 依賴注入
) { }
}
複製代碼
除此以外,咱們還能夠經過裝飾器@Global
將module A
修飾爲一個全局模塊,這樣任意module
不須要import
就能夠注入module A
中export
的依賴。同時咱們也能夠經過特定參數實現module
的動態導入。promise
Nest
是一個上層框架,底層依賴於express
框架(或hapi),因此Nest
中middleware
等同於express
中的middleware
,咱們能夠使用它來訪問request
和response
,以及對處理程序作一些前置或後置操做。它的聲明方式以下:bash
// 方式1
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable()
export class CatchMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
console.log('catch...');
next();
}
}
// 方式2
export function catchError(req, res, next) {
console.log(`catch...`);
next();
};
複製代碼
// 方式1
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(CatchMiddleware)
.forRoutes(CatsController);
}
}
// 方式2
const app = await NestFactory.create(AppModule);
app.use(catchError);
await app.listen(3000);
複製代碼
Nest
內置了處理程序未處理的異常捕獲層(exception-filter
)以及友好的異常處理對象。咱們經過聲明exception
來對catch
全局或局部的異常拋出,如全局的error
。以下面代碼所示:app
http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
// @Catch()參數爲空時,catch全部類型的異常
@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,
});
}
}
// 使用
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
複製代碼
同時咱們還能夠繼承Nest
內置的exception
對象來規範化系統異常的格式,以下代碼所示:
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
// 錯誤打印
{
"status": 403,
"error": "This is a custom message"
}
複製代碼
Pipe
在Nest
中用於對controller
中處理程序的request
作前置處理。
exception
; 這裏聲明方式能夠參考官方文檔。在Nest
中,Guard
被設計擁有單一指責的前置處理程序,用於通知route
對應處理函數是否須要執行。官方文檔中推薦使用Guard
來實現 authorization
相關的功能,如權限校驗、角色校驗等,來替代傳統express
應用程序中使用middleware
實現的思路。
官方認爲middleware
本質上是很「愚蠢」的,由於它沒法知道在調用next
以後將執行哪個處理函數。而Guard
能夠經過ExecutionContext
獲取當前request
的執行上下文,清楚的知道哪個handler
將要執行,所以能夠保證開發者在正確的request
或response
週期添加處理邏輯。
它的聲明方式以下:
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);
}
}
複製代碼
同時guard
也支持全局、單個controller
或單個handle method
進行前置處理。詳情能夠參考官方文檔。
在Nest
中,Interceptor
的設計靈感來自於[AOP](https://en.wikipedia.org/wiki/Aspect-oriented_programming)
,用於給某個處理函數模塊(如controller
)添加前置/後置操做。實現功能如:輸入/輸出的額外操做、exception
的過濾、在特定條件下重載當前處理函數的邏輯等。它的聲明方式以下:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class HttpInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// context當前執行上下文
// next下一個將要執行的處理函數
return next.handle().pipe(
map((value: any) => ({
code: 0,
data: value,
}))
);
}
}
// 使用
UseInterceptors(HttpInterceptor)
export class CatsController {}
複製代碼
以上介紹的各類Nest
組件被註冊以後由Nest
框架管理,它們在一個請求的某個聲明週期階段被執行。具體執行順序以下:
middleware
root module
)middleware
Guards
Controller Guards
route
對應處理函數上使用@UseGuards()
註冊的Guard
interceptors
(controller前置處理)controller
的interceptors
(controller前置處理)route
對應處理函數上使用@UseInterceptors()
註冊的interceptors
(controller前置處理)pipes
controller
的pipes
route
對應處理函數上使用@UsePipes()
註冊的Pipes
route
對應處理函數參數註冊的Pipes
(如:@Body(new ValidationPipe())route
對應處理函數route
對應處理函數依賴的Serivce
route
對應處理函數上使用@UseInterceptors()
註冊的interceptors
(controller後置處理)controller
的interceptors
(controller後置處理)interceptors
(controller後置處理)route
對應處理函數註冊的Exception filters
controller
註冊的Exception filters
Exception filters