从Express到Nestjs,谈谈Nestjs的设计思想和使用方法

  最近已经使用过一段时间的nestjs,让人写着有一种java spring的感受,nestjs可使用express的全部中间件,此外完美的支持typescript,与数据库关系映射typeorm配合使用能够快速的编写一个接口网关。本文会介绍一下做为一款企业级的node框架的特色和优势。java

  • 从依赖注入(DI)谈起
  • 装饰器和注解
  • nestjs的“洋葱模型”
  • nestjs的特色总结

原文在个人博客中: github.com/fortheallli…node

欢迎star和forkgit

1、从依赖注入(DI)谈起

(1)、angular中的依赖注入

  从angular1.x开始,实现了依赖注入或者说控制反转的模式,angular1.x中就有controller(控制器)、service(服务),模块(module)。笔者在早年间写过一段时间的angular1.3,下面举例来讲明:github

var myapp=angular.module('myapp',['ui.router']);
myapp.controller('test1',function($scope,$timeout){}
myapp.controller('test2',function($scope,$state){}

复制代码

  上面这个就是angular1.3中的一个依赖注入的例子,首先定义了模块名为“myapp”的module, 接着在myapp这个模块中定义controller控制器。将myapp模块的控制权交给了myapp.controller函数。具体的依赖注入的流程图以下所示:spring

default

myapp这个模块如何定义,因为它的两个控制器决定,此外在控制器中又依赖于scope、timeout等服务。这样就实现了依赖注入,或者说控制反转。typescript

(2)、什么是依赖注入

  用一个例子来通俗的讲讲什么是依赖注入。数据库

class Cat{

}
class Tiger{

}
class Zoo{
  constructor(){
     this.tiger = new Tiger();
     this.cat = new Cat();
  }
}


复制代码

  上述的例子中,咱们定义Zoo,在其constructor的方法中进行对于Cat和Tiger的实例化,此时若是咱们要为Zoo增长一个实例变量,好比去修改Zoo类自己,好比咱们如今想为Zoo类增长一个Fish类的实例变量:express

class Fish{}

class Zoo{
  constructor(){
     this.tiger = new Tiger();
     this.cat = new Cat();
     this.fish = new Fish();
  }
}

复制代码

  此外若是咱们要修改在Zoo中实例化时,传入Tiger和Cat类的变量,也必须在Zoo类上修改。这种反反复复的修改会使得Zoo类并无通用性,使得Zoo类的功能须要反复测试。json

咱们设想将实例化的过程以参数的形式传递给Zoo类:bootstrap

class Zoo{
  constructor(options){
     this.options = options;
  }
}
var zoo = new Zoo({
  tiger: new Tiger(),
  cat: new Cat(),
  fish: new Fish()
})
复制代码

  咱们将实力化的过程放入参数中,传入给Zoo的构造函数,这样咱们就不用在Zoo类中反复的去修改代码。这是一个简单的介绍依赖注入的例子,更为彻底使用依赖注入的能够为Zoo类增长静态方法和静态属性:

class Zoo{
  static animals = [];
  constructor(options){
     this.options = options;
     this.init();
  }
  init(){
    let _this = this;
    animals.forEach(function(item){
      item.call(_this,options);
    })
  }
  static use(module){
     animals.push([...module])
  }
}
Zoo.use[Cat,Tiger,Fish];
var zoo = new Zoo(options);

复制代码

  上述咱们用Zoo的静态方法use往Zoo类中注入Cat、Tiger、Fish模块,将Zoo的具体实现移交给了Cat和Tiger和Fish模块,以及构造函数中传入的options参数。

(3)、nestjs中的依赖注入

  在nestjs中也参考了angular中的依赖注入的思想,也是用module、controller和service。

@Module({
  imports:[otherModule],
  providers:[SaveService],
  controllers:[SaveController,SaveExtroController]
})
export class SaveModule {}
复制代码

  上面就是nestjs中如何定一个module,在imports属性中能够注入其余模块,在prividers注入相应的在控制器中须要用到的service,在控制器中注入须要的controller。

2、装饰器和注解

  在nestjs中,完美的拥抱了typescript,特别是大量的使用装饰器和注解,对于装饰器和注解的理解能够参考个人这篇文章:Typescript中的装饰器和注解。咱们来看使用了装饰器和注解后,在nestjs中编写业务代码有多么的简洁:

import { Controller, Get, Req, Res } from '@nestjs/common';

@Controller('cats')

export class CatsController {
  @Get()
  findAll(@Req() req,@Res() res) {
    return 'This action returns all cats';
  }
}
复制代码

  上述定义两个一个处理url为“/cats”的控制器,对于这个路由的get方法,定义了findAll函数。当以get方法,请求/cats的时候,就会主动的触发findAll函数。

  此外在findAll函数中,经过req和res参数,在主题内也能够直接使用请求request以及对于请求的响应response。好比咱们经过req上来获取请求的参数,以及经过res.send来返回请求结果。

3、nestjs的“洋葱模型”

  这里简单讲讲在nestjs中是如何分层的,也就是说请求到达服务端后如何层层处理,直到响应请求并将结果返回客户端。

在nestjs中在service的基础上,按处理的层次补充了中间件(middleware)、异常处理(Exception filters)、管道(Pipes),守卫(Guards),以及拦截器(interceptors)在请求到打真正的处理函数之间进行了层层的处理。

default

上图中的逻辑就是分层处理的过程,通过分层的处理请求才能到达服务端处理函数,下面咱们来介绍nestjs中的层层模型的具体做用。

(1)、middleware中间件

  在nestjs中的middle彻底跟express的中间件一摸同样。不只如此,咱们还能够直接使用express中的中间件,好比在个人应用中须要处理core跨域:

import * as cors from 'cors';
async function bootstrap() {
  onst app = await NestFactory.create(/* 建立app的业务逻辑*/)
  app.use(cors({
    origin:'http://localhost:8080',
    credentials:true
  }));
  await app.listen(3000)
}
bootstrap();

复制代码

在上述的代码中咱们能够直接经过app.use来使用core这个express中的中间件。从而使得server端支持core跨域等。

初此以外,跟nestjs的中间件也彻底保留了express中的中间件的特色:

  • 在中间件中接受response和request做为参数,而且能够修改请求对象request和结果返回对象response
  • 能够结束对于请求的处理,直接将请求的结果返回,也就是说能够在中间件中直接res.send等。
  • 在该中间件处理完毕后,若是没有将请求结果返回,那么能够经过next方法,将中间件传递给下一个中间件处理。

在nestjs中,中间件跟express中彻底同样,除了能够复用express中间件外,在nestjs中针对某一个特定的路由来使用中间件也十分的方便:

class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}
复制代码

上面就是对于特定的路由url为/cats的时候,使用LoggerMiddleware中间件。

(2)、Exception filters异常过滤器

  Exception filters异常过滤器能够捕获在后端接受处理任何阶段所跑出的异常,捕获到异常后,而后返回处理过的异常结果给客户端(好比返回错误码,错误提示信息等等)。

  咱们能够自定义一个异常过滤器,而且在这个异常过滤器中能够指定须要捕获哪些异常,而且对于这些异常应该返回什么结果等,举例一个自定义过滤器用于捕获HttpException异常的例子。

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}
复制代码

  咱们能够看到host是实现了ArgumentsHost接口的,在host中能够获取运行环境中的信息,若是在http请求中那么能够获取request和response,若是在socket中也能够获取client和data信息。

  一样的,对于异常过滤器,咱们能够指定在某一个模块中使用,或者指定其在全局使用等。

(3)、Pipes管道

  Pipes通常用户验证请求中参数是否符合要求,起到一个校验参数的功能。

  好比咱们对于一个请求中的某些参数,须要校验或者转化参数的类型:

@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;
  }
}

复制代码

  上述的ParseIntPipe就能够把参数转化成十进制的整型数字。咱们能够这样使用:

@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}

复制代码

  对于get请求中的参数id,调用new ParseIntPipe方法来将id参数转化成十进制的整数。

(4)、Guards守卫

  Guards守卫,其做用就是决定一个请求是否应该被处理函数接受并处理,固然咱们也能够在middleware中间件中来作请求的接受与否的处理,与middleware相比,Guards能够得到更加详细的关于请求的执行上下文信息。

一般Guards守卫层,位于middleware以后,请求正式被处理函数处理以前。

下面是一个Guards的例子:

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

复制代码

这里的context实现了一个ExecutionContext接口,该接口中具备丰富的执行上下文信息。

export interface ArgumentsHost {
  getArgs<T extends Array<any> = any[]>(): T;
  getArgByIndex<T = any>(index: number): T;
  switchToRpc(): RpcArgumentsHost;
  switchToHttp(): HttpArgumentsHost;
  switchToWs(): WsArgumentsHost;
}

export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}

复制代码

除了ArgumentsHost中的信息外,ExecutionContext还包含了getClass用户获取对于某一个路由处理的,控制器。而getClass用于获取返回对于指定路由后台处理时的处理函数。

对于Guards处理函数,若是返回true,那么请求会被正常的处理,若是返回false那么请求会抛出异常。

(5)、interceptors拦截器

   拦截器能够给每个须要执行的函数绑定,拦截器将在该函数执行前或者执行后运行。能够转换函数执行后返回的结果等。

归纳来讲:

interceptors拦截器在函数执行前或者执行后能够运行,若是在执行后运行,能够拦截函数执行的返回结果,修改参数等。

再来举一个超时处理的例子:

@Injectable()
export class TimeoutInterceptor implements NestInterceptor{
  intercept(
    context:ExecutionContext,
    call$:Observable<any>
  ):Observable<any>{
    return call$.pipe(timeout(5000));
  }
}
复制代码

该拦截器能够定义在控制器上,能够处理超时请求。

4、nestjs的特色总结

  最后总结一下nestjs的优缺。

nestjs的优势:

  • 完美的支持typescript,所以可使用日益繁荣的ts生态工具

  • 兼容express中间件,由于express是最先出现的轻量级的node server端框架,nestjs可以利用全部express的中间件,使其生态完善

  • 层层处理,必定程度上能够约束代码,好比什么时候使用中间件、什么时候须要使用guards守卫等。

  • 依赖注入以及模块化的思想,提供了完整的mvc的链路,使得代码结构清晰,便于维护,这里的m是数据层能够经过modules的形式注入,好比经过typeorm的entity就能够在模块中注入modules。

  • 完美支持rxjs

相关文章
相关标签/搜索