NestJS 核心特性及概念梳理 「持续补充」
前言
刚入职新公司,接手到一个NestJS的项目(终于不用写HTML 和 CSS了),用于做前后端API交互的中间层处理,之前也有用NestJS写过一些自己的Demo项目,但是没太系统性的深入学习,现在趁着这次机会系统性的学习一下NestJS,顺便整理成文档…
NestJS是什么?
以下是官网的原话
Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,内置并完全支持 TypeScript(但仍然允许开发人员使用纯 JavaScript 编写代码)并结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素。
在底层,Nest使用强大的 HTTP Server 框架,如 Express(默认)和 Fastify。Nest 在这些框架之上提供了一定程度的抽象,同时也将其 API 直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。
Nest 提供了一个开箱即用的应用程序架构,允许开发人员和团队创建高度可测试,可扩展,松散耦合且易于维护的应用程序。
加上我自己的了解,总结一下分为几个特征
- 是一个NodeJS的框架
- 内置支持Typescript
- 支持面向对象编程(OOP) / 函数式编程(FP)/ 函数式响应编程(FRP)
- HTTP 服务器
- Express (默认)
- Fastify
- 核心特性
- 依赖注入 - DI
- 中间件 - Middleware
- 守卫 - Guards
- 拦截器 - interceptors
- 管道 - Pipe
- 控制器 - Controller
- 模块 - Module
- 微服务
本文主要谈及一些和其他Node框架稍微差异的特性,比如
依赖注入
、控制器
、管道
、拦截器
、模块
、微服务
。
本文章先从Nest的一个请求的生命周期开始讲解
Nest的一次请求生命周期
请求发起: 中间件 => 守卫 => 拦截器 => 管道 => 控制器
⬇️
响应响应: HTTP Response <= 拦截器 <= 控制器
这次没画图了,以后找到好用的画图工具补一下 😅
依赖注入
在Nest中使用了大量的装饰器语法,依赖注入也是通过装饰器的形式进行实现
使用 @Injectable() 装饰的类,可以在任何地方被注入,下面看依赖注入在Nest中的具体使用
Provides
是Nest的最基本的一个概念,许多基本的Nest类可能视为provider-service,repository,helper等等,在实际开发中,比如常用的service, repository。有了依赖注入我们能够提高应用程序的灵活性和模块化程度。举个例子说明:
app.module.t
import { Module } from '@nestjs/common'; |
app.services.ts
import { Injectable } from '@nestjs/common'; |
app.controller.ts
通过该装饰器使Nest知道这个类是一个provider,现在我们使用类构造函数注入该服务
import { Controller, Get } from '@nestjs/common'; |
从上面代码来看, 我们在Controller里使用AppService不是通过使用New来实例化, 而是在constuctor声明即可。
可看出依赖注入的两个优势:
- 依赖管理交给Nest运行时处理
- 依赖项只关注注入类型,不关注具体实例,具有高度解偶性
控制器
一般Node框架(Express,Koa)都有没有控制器这个概念,除了阿里的Egg这种上层的封装框架,Nest 的控制器相当于路由的概念
守卫
未完待续。。。
管道
管道是具有 @Injectable()
装饰器的类。管道应实现 PipeTransform
接口
当在 Pipe 中发生异常,controller 不会继续执行任何方法。
管道有两个类型:
- 转换:管道将输入数据转换为所需的数据输出
- 验证:对输入数据进行验证,比如form表单提交的数据类型
在这两种情况下, 管道 参数(arguments)
会由 控制器(controllers)的路由处理程序 进行处理. Nest 会在调用这个方法之前插入一个管道,管道会先拦截方法的调用参数,进行转换或是验证处理,然后用转换好或是验证好的参数调用原方法。
转换管道使用
validation.pipe.ts
import { PipeTransform, ArgumentMetadata } from '@nestjs/common'; |
blog.controller.ts
import { ValidationPipe } from '@pipe/validation.pipe.ts' |
验证管道使用
validation.pipe.ts
import { PipeTransform, ArgumentMetadata } from '@nestjs/common'; |
blog.controller.ts
参数验证管道,在要验证的管道的前面加上 new Pipe()
将参数作为 pipe
实例里的 transform
方法中的第一个参数
import { ValidationPipe } from '@pipe/validation.pipe.ts' |
拦截器
拦截器(在使用之前进行拦截):对拦截器装饰的方法的返回值进行拦截。 明白这一点后,就很容易理解 为什么要要手动调用 CallHeadler.handle() 才会触发到被拦截的方法了。
好处
- 提前发现处理异常
- 对操作进行收拢,统一处理
每个拦截器都有 intercept()
方法,它接收2个参数。
ExecutionContext
实例(与守卫完全相同的对象)ExecutionContext
继承自ArgumentsHost
。ArgumentsHost
是传递给原始处理程序的参数的一个包装 ,它根据应用程序的类型包含不同的参数数组CallHandler - 调用处理程序,调用这个参数的
handle()
方法 (并且已经返回值)被拦截器包裹的方法才被触发,handle() 返回一个Observable
,可以帮助我们进行例如响应操作。比如 CatsController 中的 create 方法用了拦截器,在这个拦截器中的 intercept方法内,必须调用 CallHandler.handler() 方法,create才会被执行。不调用CallHandler则不执行create()
拦截器的使用(局部)
拦截器可以装饰在
在class上 - 这种方式,控制器 实例 的每个方法触发,都会执行拦截器
在method 上 - 只对当前装饰的方法有效
auto.interceptor.ts
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; |
auto.interceptor.ts
把拦截器装饰在class上
import { AutoInterceptor } from '../interceptors/auto.interceptor'; |
auto.interceptor.ts
把拦截器装饰在 method上
export class BlogControll { |
全局拦截器
全局拦截器用于整个应用程序、每个控制器和每个路由处理程序。
这种方式注入不属于任何模块,因此也无法注入依赖项
const app = await NestFactory.create(ApplicationModule); |
为了解决此问题, 您可以使用以下构造直接从 根模块 设置一个拦截器:
app.module
import { Module } from '@nestjs/common'; |