我为什么喜欢NestJS

欢迎关注我的公众号睿Talk,获取我最新的文章:
clipboard.png

一、前言

做过 Java EE 开发的朋友对 Spring 框架应该很熟悉了,它全面的功能和优秀的设计是得以广泛流行的原因。它通过灵活使用控制反转、依赖注入和面向切面编程等设计理念,极大的规范了大型应用的架构,降低了模块之间的耦合度,从而提升了应用的开发效率。在 NodeJS 的世界里,也存在一个全面借鉴 Spring 设计思想的框架,它在 github 上有将近 2w 的 star,npm 的周下载量超过 11w,它就是本文要介绍的 NestJS

二、与其它框架的对比

市面上 NodeJS 的服务端框架有很多,如KoaExpressEggJSMidway等,它们功能都很强大,也有很好的生态,插件非常丰富,为什么还需要Nest呢?

如果是一个简单的应用,其实用什么框架都无所谓,一个框架用 100 行代码实现,另一个用 80 行,区别不大。但涉及到企业级的应用,分分钟有上万行的代码,代码的组织结构就变得很重要了。如果代码拆分不合理,一个 JS 文件就有上千行的代码,后期的维护成本会非常的高。再考虑到复杂项目参与者众多,没有一个规范去约束的话,每个人写出来的代码风格迥异,协作起来会很难受。上文提到的几个框架对项目代码的架构要么是没约束,要么就是约束比较弱或者看起来很别扭。相比之下Nest的实现就很简洁,用起来很顺手。具体细节将在下文进行描述。

Nest还通过依赖注入的形式实现了控制反转,只要声明模块中的依赖,Nest就会在启动的时候去创建依赖,然后自动注入到相应的地方。依赖注入最大的作用是代码解耦,依赖的对象根据不同的情况可以有多种实现,如单元测试的时候可以在不改业务代码的情况下将依赖的对象换成 Mock 数据。

Nest还践行了面向切面编程的思想,除了Middleware外,还有Exception FilterPipesGuardsInterceptors几个预定义的切面,可以集中进行异常处理、数据验证、权限验证和逻辑扩展等功能。Nest自带如数据验证等一些常用的基于切面的功能,也可以通过继承的方式来进行扩展。这些预定义的切面是代码架构的组成部分,按照这些约定来组织代码会大大降低日后的维护成本。

类型系统是后端开发很重要的一环,Nest是使用TypeScript实现的框架,因此原生就支持TypeScript,而且还大量使用了注解,熟悉 Spring 的朋友会感到十分亲切。

另外,Nest是基于Express实现的,需要的话可以取到底层的对象,如requestresponse

三、实战

下面的讲解将会基于一个简单的增删改查 API 服务器,完整项目代码在这里,在此就不一步步去介绍编写过程了。

  • 模块化

Nest是以模块的形式组织项目的,模块中可以声明ControllerProviderImportExport。打开app.module.ts,内容如下:

@Module({
  imports: [CatsModule, MongooseModule.forRoot('mongodb://localhost/nest')],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

可以看到项目的根模块AppModule导入了项目中的另一个模块CatsModule和外部依赖MongooseModule。另外也声明了模块内部的ControllerProvider。我们一般说的ServiceProvider的一种。ModuleControllerProvider的关系见下图:

项目结构

ControllerProvider都在Module注册,容器会将Provider注入到Controller中,Module之间可以相互引用(Import)。像 ES6 的模块化一样,Import后只能使用别人Export出来的内容。

  • 注解

再来看一下cats.controller.ts

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get(':name')
  async findOne(@Param('name') name: string): Promise<Cat> {
    return this.catsService.findOne(name);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }

  @Post()
  @HttpCode(201)
  @Header('Cache-Control', 'none')
  async create(
    @Body(new ValidationPipe()) createCatDto: CreateCatDto,
  ): Promise<Cat[]> {
    return this.catsService.create(createCatDto);
  }
}

这文件有大量的注解,这是Nest有别于其它 NodeJS 框架的地方,像极了 Spring。很多注解的含义也与 Spring 的一致,像这里的@Controller@Get@Post都是用来声明路由和 http 请求类型的。@Get(':name')是获取 url 的参数,而@Param('name')是获取请求体的参数。@Body(new ValidationPipe()) createCatDto: CreateCatDto这行代码做了很多事,首先将请求体取出,然后校验数据类型是否合规,然后再将请求体转换为 DTO 对象供后续使用。DTO 的定义如下,也是通过注解定义校验逻辑:

export class CreateCatDto {
  @IsString()
  readonly name: string;
  @IsNumber()
  readonly age: number;
  @IsString()
  readonly breed: string;
}
  • 切面

上面提到的ValidationPipe是内置的Pipe切面,用于校验参数类型。另外几种切面和请求处理的顺序见下图:

切面

这里的Middleware就是Express原生的,其它几个切面的用法见官方文档,在此不多作介绍。

  • 连接数据库

例子中使用mongoose连接和操作本地MongoDB数据库。为了更方便使用,Nest提供了@nestjs/mongoose包,对mongoose包装了一层,使其更符合Nest的使用风格。操作数据库的步骤如下:

  • app.module中定义连接的数据库:MongooseModule.forRoot('mongodb://localhost/nest')
  • cat.schema中定义 Schema
  • cats.module中声明依赖 Model:MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }])
  • cats.service中注入依赖 Model:constructor(@InjectModel('Cat') private readonly catModel: Model<Cat>) {}
  • cats.service中使用 Model:this.catModel.findOne({ name }).exec()

四、总结

本文重点介绍了Nest的设计思想,比较了它跟其它框架的异同,并结合实例详细讲解了具体的用法。文章的写作目的是为框架选型者提供一个快速的参考,也为对Nest感兴趣的人提供感性的认识。如果想更详细的了解Nest用法,请看官方文档

P.S.腾讯云招前端工程师,欢迎加我微信:

图片描述


睿Talk
记录个人和团队技术成长的点点滴滴
5.5k 声望
422 粉丝
0 条评论
推荐阅读
Web前端主题切换的几种方案
这种方案利用了css多层样式精确匹配的特点,通过样式覆盖的方式实现主题的切换。首先需要在应用的根元素中设一个 class,切换主题时给 class 赋上对应的值,下面以theme1/theme2为例。

Dickens8阅读 8.2k

封面图
反编译微信小程序获取小程序前端源码wxapkg
研究反编译的原因就是我自己辛苦了半个月写的小程序,忘记备份放在桌面,心急体验Win11系统 重装系统忘记备份源码,后悔莫及。 后来网上找了反编译的教程,反编译已经上线了的小程序 于是自己尝试了一下,真的可...

TANKING12阅读 10k评论 7

封面图
PDF 预览和下载你是怎么实现的?
在开发过程中要求对 PDF 类型的发票提供 预览 和 下载 功能,PDF 类型文件的来源又包括 H5 移动端 和 PC 端,而针对这两个不同端的处理会有些许不同,下文会有所提及。

熊的猫7阅读 3.7k评论 1

封面图
Just for fun——C#应用和Nodejs通讯
进程通信常见的进程通讯的方法有:管道(Pipe)命名管道信号消息队列其他管道是比较简单基础的技术了,所以看看它。Node IPC支持Node官方文档中Net模块写着:IPC SupportThe net module supports IPC with named ...

pigLoveRabbit3阅读 6.8k评论 2

「过程详解」async await综合题
如果你之前跟我一样一直对async await熟悉又陌生的话(熟悉是可能每天都在用,陌生是针对一些组合题又丈二和尚摸不着头脑),不妨可以边看边练,总结规律,相信会逐渐清晰并有所得。本文对每个案例都详细描述了代...

wuwhs5阅读 1.2k

封面图
cligetter|一款快速生成 Cli工具 开发模版的脚手架
近年来 cli工具 的开发,对于不断发展的前端生态来说,似乎也逐渐成为工程师们的必备技能。其实开发一个 cli工具 并不难,但对于前端的同学可能存在一点认知上的小门槛,特别是对于刚开始接触 cli 脚手架工具开发...

木木剑光3阅读 649

一个灵活的 Node.js 多功能爬虫库 —— x-crawl
x-crawl · x-crawl 是一个灵活的 Node.js 多功能爬虫库。灵活的使用方式和众多的功能可以帮助您快速、安全、稳定地爬取页面、接口以及文件。如果你也喜欢 x-crawl ,可以给 x-crawl 存储库 点个 star 支持一下,...

coderhxl2阅读 1.9k评论 2

封面图
5.5k 声望
422 粉丝
宣传栏