欢迎关注个人公众号睿Talk
,获取我最新的文章:前端
作过 Java EE 开发的朋友对 Spring 框架应该很熟悉了,它全面的功能和优秀的设计是得以普遍流行的缘由。它经过灵活使用控制反转、依赖注入和面向切面编程等设计理念,极大的规范了大型应用的架构,下降了模块之间的耦合度,从而提高了应用的开发效率。在 NodeJS 的世界里,也存在一个全面借鉴 Spring 设计思想的框架,它在 github 上有将近 2w 的 star,npm 的周下载量超过 11w,它就是本文要介绍的 NestJS。git
市面上 NodeJS 的服务端框架有不少,如Koa
、Express
、EggJS
、Midway
等,它们功能都很强大,也有很好的生态,插件很是丰富,为何还须要Nest
呢?github
若是是一个简单的应用,其实用什么框架都无所谓,一个框架用 100 行代码实现,另外一个用 80 行,区别不大。但涉及到企业级的应用,分分钟有上万行的代码,代码的组织结构就变得很重要了。若是代码拆分不合理,一个 JS 文件就有上千行的代码,后期的维护成本会很是的高。再考虑到复杂项目参与者众多,没有一个规范去约束的话,每一个人写出来的代码风格迥异,协做起来会很难受。上文提到的几个框架对项目代码的架构要么是没约束,要么就是约束比较弱或者看起来很别扭。相比之下Nest
的实现就很简洁,用起来很顺手。具体细节将在下文进行描述。mongodb
Nest
还经过依赖注入的形式实现了控制反转,只要声明模块中的依赖,Nest
就会在启动的时候去建立依赖,而后自动注入到相应的地方。依赖注入最大的做用是代码解耦,依赖的对象根据不一样的状况能够有多种实现,如单元测试的时候能够在不改业务代码的状况下将依赖的对象换成 Mock 数据。typescript
Nest
还践行了面向切面编程的思想,除了Middleware
外,还有Exception Filter
、Pipes
、Guards
和Interceptors
几个预约义的切面,能够集中进行异常处理、数据验证、权限验证和逻辑扩展等功能。Nest
自带如数据验证等一些经常使用的基于切面的功能,也能够经过继承的方式来进行扩展。这些预约义的切面是代码架构的组成部分,按照这些约定来组织代码会大大下降往后的维护成本。数据库
类型系统是后端开发很重要的一环,Nest
是使用TypeScript
实现的框架,所以原生就支持TypeScript
,并且还大量使用了注解,熟悉 Spring 的朋友会感到十分亲切。npm
另外,Nest
是基于Express
实现的,须要的话能够取到底层的对象,如request
和response
。编程
下面的讲解将会基于一个简单的增删改查 API 服务器,完整项目代码在这里,在此就不一步步去介绍编写过程了。segmentfault
Nest
是以模块的形式组织项目的,模块中能够声明Controller
、Provider
、Import
和Export
。打开app.module.ts
,内容以下:后端
@Module({ imports: [CatsModule, MongooseModule.forRoot('mongodb://localhost/nest')], controllers: [AppController], providers: [AppService], }) export class AppModule {}
能够看到项目的根模块AppModule
导入了项目中的另外一个模块CatsModule
和外部依赖MongooseModule
。另外也声明了模块内部的Controller
和Provider
。咱们通常说的Service
是Provider
的一种。Module
、Controller
和Provider
的关系见下图:
Controller
和Provider
都在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
中定义 Schemacats.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
用法,请看官方文档。