NestJS是一个Node服务端开发框架,底层封装的是Express,类似的有阿里的Midway(基于Koa)。这类框架都是提供了标准化的开发规范,以及开箱即用的工具,让开发人员专注业务开发,并输出更告质量的代码。
核心概念
1、Controller
一个Controller就是一个Class,用于处理请求和响应的。根据请求的地址指定处理程序并返回对应结果,也就是充当路由的作用。Controller的配置是利用装饰器,简单高效。
典型用法
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
请求相关
1)路由配置
装饰器@Controller修饰Class,用来配置Controller类的公共路由地址,地址可以传空,传空就表示访问根目录的响应。
@Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), @All(), @Head修饰类方法,用来配置对应请求的处理程序,可以传入path,和@Controller的path组成一个完整的路由路径(也就是说路由路径和装饰器修饰的方法名没关系,不过建议和path保持一致)。
注意路由配置支持通配符,如@Controller(hello*)会匹配所有http://127.0.0.1:3000/hello 开头的请求。
如下配置用Get请求 http://127.0.0.1:3000/ 返回 “Get Hello word”,用Post方法请求就返回 “Post Hello word”,用Get请求 http://127.0.0.1:3000/hello 返回 “hello”。
import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return 'Get Hello word';
}
@Get('hello')
getHello(): string {
return 'hello';
}
@Post()
postHello(): string {
return 'Post Hello word';
}
}
2)Reqeust对象
默认情况下,NestJS封装的请求对象和Express的Reqeust对象相同,并且 NestJS 提供了一些 Request属性的快捷访问。请求相关信息是通过 Controller类的方法参数注入的,需要用相关装饰器修饰,常用修饰器和作用如下:
3) 请求参数
获取请求参数通过读取 Request对象提供的 params、body 属性。NextJS 支持RESTful风格接口设计,因此还支持路由传参,这时通过request.params获取的就是路由参数。
// http://127.0.0.1:3000/hello/111?test=1sdf&123=1 返回 Get Hello word: {"0":"","id":"111"}
@Get(':id')
getHello(@Req() request: Request): string {
return 'Get Hello word: ' + JSON.stringify(request.params);
}
响应相关
修改响应报文使用NestJS提供的装饰器即可。
- 修改状态码使用
@HttpCode - 修改报文头使用
@Header - 重定向使用
@Redirect
响应内容直接在Controller类的方法中返回响应内容即可,NestJS会自动按最佳返回格式返回内容。
2、Provider
Provider是一个抽象概念,一个Provider具有以下特点:
- 纯类(类别纯函数),使用 @Injectable 修饰;
- 可以作为其他模块的依赖,并被IoC容器管理;
由于Provider具有以上特点,默认情况下,一个Provider类会在服务启动时初始化(没有被引用的Provider也会初始化),并且全局只有一个实例,即使这个Provider类被多出引用。
使用Provider的好处是,类的实例化交给了NestJS,消费方只需要声明依赖即可,至于依赖什么时候初始化,什么时候销毁都不用管。
1)Provider声明方式
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;
}
}
2)Provider注入方式
使用constructor声明
import { Controller } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
}
等价于
@Controller('cats')
export class CatsController {
private catsService: CatsService;
constructor(catsService: CatsService) {
this.catsService = catsService;
}
}
3、Modules
使用 @Module() 修饰的类就是一个Module。Module的作用就是用来组织代码的。
NestJS将模块分成了四种类型
- providers 就是上文的Provider,必须在Module中声明,不然 NestJS 无法做到IoC
- controllers 就是上文说的Controller
- imports 用来引入其他模块,引入后其他模块的Provider(需要exports声明)和Controller就可以直接使用了
- exports 用来导出本模块的Provider,声明后其他模块就可以引入使用了。
异步模块
4、Middleware
默认情况下NestJS的Middleware等价于Express的Middleware。
Middleware声明
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class LogMiddlewareMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.info('LogMiddlewareMiddleware', req.query);
next();
}
}
Middleware使用
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LogMiddlewareMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL });
}
}
Middleware特点
- 可以执行任何代码;
- 执行时可获得Request和Respond对象,并可以修改;
- 可以中断请求处理流程,只要不执行next函数即可;
- 执行了next函数就表示将请求处理交给后续流程,如其他Middleware;
- 每个请求流程一个Middleware只会执行一次;
执行的顺序是先执行Middleware(多个按注册顺序),再执行Controller逻辑。
4、Exception filters
Exception filter是用来处理服务异常的,当服务出现未捕获异常时,一般要记录日志,返回友好提示或重定向。 异常捕获本身是一个很重要的话题,值得花心思去好好规划服务的异常处理体系,NestJS的Exception filter既是异常处理工具,也是一份异常处理指南。
1)捕获异常
使用@Catch装饰器即可声明一个异常捕获,支持传入多个参数,当不传入参数时表示捕获所有异常,传入参数就表示捕获指定类型的异常。
2)异常处理
implements ExceptionFilter实现catch方法即可,该方法能获取异常对象(HttpException)和执行上下文(ArgumentsHost)。
示例代码
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,
});
}
}
3)插入异常捕获点
- 捕获某个方法的异常
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
- 捕获整个Controller的异常
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
- 捕获全局异常
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
内置Exception Filter
NestJS内置了一个全局的HttpException Filter,当服务发生未捕获异常时,会默认返回
{
"statusCode": 500,
"message": "Internal server error"
}
返回的内容可以自定义,只需要抛出一个HttpException或继承该类的Exception即可,如:
// 自定义异常类
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
// Controller中抛出异常
@Get()
async findAll() {
throw new ForbiddenException();
}
5、Pipes
Pipe是一个实现了PipeTransform接口的Provider。主要用于处理请求入参和校验入参,校验失败时可以抛出异常(然后被Exception Filter处理)。
因为是处理和校验入参的,因此Pipe使用位置肯定是在Controller的方法参数上,并且实在方法执行前执行。
// 以下配置表示:query参数id必须是整数类型或能转成整数类型,不然就会报错
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
内置的Pipe
ValidationPipeParseIntPipeParseFloatPipeParseBoolPipeParseArrayPipeParseUUIDPipeParseEnumPipeDefaultValuePipeParseFilePipe
自定义Pipe
实现transform方法即可,转换和校验放一个方法,校验不通过抛出异常即可,可以利用第三方校验工具配合使用,如下面使用的joi。
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ObjectSchema } from 'joi';
@Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private schema: ObjectSchema) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = this.schema.validate(value);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
6、Guards
Guard是一个实现了CanActivate的Provider。作用如其名,守护对象是Route,也就是作用于Controller,用来判断请求是否能被Controller处理,最典型的就是登录验证了。
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
// 该方法返回true就可以继续往后走,返回false本次请求就返回错误
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
Guard能做的事情用Middleware也能实现,那为啥要再搞一个Guard呢?
- Middleware不够灵活,因为不知道next执行后的后续处理程序,对于Middleware注册位置和next的执行时机需要十分注意,不然很容易导致意外的拦截。
7、Interceptor
Interceptor是一个实现了NestInterceptor的Provider。主要用来实现AOP(面向切面编程)。