nestJS学习笔记
快速教程:
概述
nest特点:
- 原生支持TypeScript的框架
- 可以基于Express也可以选择fastify
- 使用IOC模式(控制反转),依赖注入和装饰器
- 丰富的内置插件:管道 守卫 异常过滤器
项目创建
npm i -g @nestjs/cli // 全局安装Nest
nest new project-name // 创建项目
注意: Nest.js 要求 Node.js(>= 10.13.0,v13 除外), 如果你的Node.js 版本不满足要求,可以通过nvm包管理工具安装符合要求的Node.js版本
项目结构
app.controller.ts | 单个路由的基本控制器(Controller) |
---|---|
app.controller.spec.ts | 针对控制器的单元测试 |
app.module.ts | 应用程序的根模块(Module) |
app.service.ts | 具有单一方法的基本服务(Service) |
main.ts | 应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例。 |
管道实际上是用来做数据验证的, 正常的逻辑需要写一堆if else去判断 但是nest提供 了管道
定义:
管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口。
管道有两个类型:
- 转换:管道将输入数据转换为所需的数据输出
- 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常;
nest设计模式
IOC:控制反转,具体定义是高层模块不应该依赖底层模块 二者都应该依赖其抽象,抽象不应该依赖细节,细节应该抽象。
DI: 依赖注入 其实和IOC原本是一个东西 ,由于控制反转概念比较模糊所以提出了新的名字:依赖注入 类A依赖类B的常规表现是在A中使用B的instance
控制反转是nest去做的事情,依赖注入则是我们需要做的事情
具体使用:
1在service中使用@Injectable()装饰器
2 module中的providers导入
3 在controller中导入并使用
装饰器
装饰器是一种特殊的类型声明,可以附加在类,方法属性上面
如果写的装饰器没生效需要配置tsconfig.json中的属性
类装饰器
主要通过@符号添加装饰器 会把class的构造函数传入到装饰器的第一个参数target中去
然后通过prototype可以自定义添加属性和方法
function decotators (target:any) {
target.prototype.name = 'pucai'
}
@decotators
class Xiaoman {
constructor () {
}
}
const xiaoman:any = new Xiaoman()
console.log(xiaoman.name)
属性装饰器
使用@符号给属性添加装饰器 返回两个参数 1 原型对象 2 属性的名称
const currency: PropertyDecorator = (target: any, key: string | symbol) => {
console.log(target, key)
}
class Xiaoman {
@currency
public name: string
constructor() {
this.name = ''
}
getName() {
return this.name
}
}
参数装饰器
使用@符号添加装饰器 返回两个参数 1 原型对像那个 2 方法的名称
方法装饰器
同样使用@符号给属性添加装饰器 返回参数 1 原型对象 2 方法的名称
egg和nestJS区别
1、Egg.js是和Nest.js 都是为企业级框架和应用而生。
2、 Egg.js和Nest.js都是非常优秀的Nodejs框架。Egg.js基于Koa,Nest.js默认基于Express,nest也可以基于其他框架
3、 Egg.js文档相比Nestjs优秀很多
4、 Express、Koa 是 Node.js 社区广泛使用的框架,简单且扩展性强,非常适合做个人项目。但框架本身缺少约定,标准的 MVC 模型会有各种千奇百怪的写法。Egg 和Nestjs都是按照约定进行开发的。但是Egg相比Nestjs约定更标准。
5、 面向对象方面Nestjs优于Egg.js,Nestjs基于TypeScript, 如果你会angular或者java学习Nestjs非常容易。
egg特性:
提供基于 Egg 定制上层框架的能力
高度可扩展的插件机制
内置多进程管理
基于 Koa 开发,
性能优异 框架稳定,
测试覆盖率高
渐进式开发
nest特性:
依赖注入容器
模块化封装
可测试性
内置支持 TypeScript
可基于Express或者fastify
文章:
依赖的底层框架
egg nest
koa框架 express框架
两大基础框架的优缺点
Express:express框架历史悠久,路由已经封装在express内部了,调用比较方便,但是express中的callback hell这个处理起来就比较麻烦
Koa:express是比较年轻的框架,他使用了async/await这种异步回调函数大大减少了callback的麻烦,但是koa的路由有引入新的插件才可以解决。
开发文档
Egg:说实话egg的开发文档还是比较完美的,东西写的比较全也很齐
Nest:nest的开发文档就比较一般般,可以区他们官方文档对比一下就知道了。
模块或者插件
Egg:在egg上面,插件是使用加载然后挂载的原理挂载到各个对象,这个优点就是基于egg中的loader和egg-core
Nest:在nest模块加载方面,主要使用的是模块容器-依赖注入(通过装饰器和元数据实现)
代码风格
Egg: 在egg里面的代码风格主要是Classify,在egg里面有私有属性与慢初始化的特点。
Nest: 在nest里面的风格主要是ts风格,如果你会java或者前端的Angular框架可能就对nest的代码风格比较熟悉。
特点
Egg:在egg里面我很喜欢它的一个特点“约束优于配置”,有这个特点就可以减少企业制定的规则。Egg还可以制上层框架的能力,就是企业可以根据自己的需求开发出属于自己的框架。还有高度可扩展的插件机制 内置多进程管理。
Nest:在nest里面最大的特点可能就是,依赖注入容器,模块化封装,ts风格,模块封装就是每一个应用的每个逻辑都是一个模块,而且可以封装
控制器
Controller Request (获取前端传过来的参数)
nestjs提供的方法参数装饰器,用来帮助我们快速获取参数如下
@Request() req
@Response() res
@Next() next
@Session() req.session
@Param(key?: string) req.params/req.params[key]
@Body(key?: string) req.body/req.body[key]
@Query(key?: string) req.query/req.query[key]
@Headers(name?: string) req.headers/req.headers[name]
@HttpCode
路由
控制器的目的是接受应用的特定丢丢,路由机制控制哪个控制器接受请求。
路径路由: 一个处理程序的路由路径是通过连接为控制器声明的前缀和请求装饰器中指定的任何路径来确定的,
request (装饰器)
在签名中使用 @Req() 装饰器,指示 Nest 将请求对象注入处理程序。
装饰器有很多,都对照着底层的属性
@Res() 只是 @Response() 的别名。两者都直接暴露了底层平台的 response 对象接口,兼容不同的平台Express 和 Fastify
Resources
也就是对应标准http的方法装饰器:@Put()、@Delete()、@Patch()、@Options()、以及 @Head()。此外,@All() 则用于定义一个用于处理所有 HTTP 请求方法的处理程序。
Route wildcards (路由通配符)
*号作为路由通配符, 匹配对应的路由路径
Status code 状态码
默认状态码是200 post是201 可以使用处理函数外添加@HttpCode(204)装饰器修
Headers
自定义响应头,可以使用 可以使用 @header() 装饰器或类库特有的响应对象,(并直接调用 res.header())
Header 需要从 @nestjs/common 包导入。
重定向
重定向有两个参数 url和statusCode 默认code为302
路由参数
@Param() 用于修饰一个方法的参数(上面示例中的 params),并在该方法内将路由参数作为被修饰的方法参数的属
Param 需要从 @nestjs/common 包导入。
最后
创建好控制器之后 需要在模块中定义
app.module.ts中
Providers 提供者
是一个nest的基本概念, 很多类都被视为Providers 通过constructor注入依赖关系。并且连接对象实例的功能可以委托给nest运行时系统, Provider 只是一个用 @Injectable() 装饰器注释的类。
Services 服务
服务类统一通过cli创建:$ nest g service cats 主要是用来扩展 控制器上一些服务的方法
Dependency injection 依赖注入
在nest中 借助typescript可以比较容易的管理依赖,优势是仅仅按照类型进行解析,
作用域 自定义提供者 可选提供者
nest有一个内置的控制反转 IOC容器 可以解决providers之间的关系, 在providers中 还有普通值,类,异步 或者同步工厂等等,
Modules 模块
模块是具有 @Module() 装饰器的类。 @Module() 装饰器提供了元数据,Nest 用它来组织应用程序结构。
每个nest应用至少有一个模块(根模块)。每个模块一般都是一个独立的功能。
装饰器接受一个描述模块属性的对象:
Feature modules 功能模块
当一个controller和service属于同一个应用程序域(服务同一个类)。可以移动到一个功能模块下,
使用cli创建模块:$ nest g module cats
最后再把这个模块导入到根模块中(ApplicationModule)。
Shared modules 共享模块
nest中默认模块是单例的,可以在多个模块中共享同一个提供者实例,
举例:假设我们将在几个模块之间共享 CatsService 实例。 我们需要把 CatsService 放到 exports 数组中
Module re-exporting模块导出
实际上就是在Module数组中导出内部提供者
Dependency injection依赖注入
providers也可以导入到模块中去 在数组中使用providers:[CatsService],
Global modules全局模块
全局模块需要使用到@Global 装饰器,只注册一次(其他模块在使用的时候注册)
提示: 最好全部都使用全局模块,正常推荐使用imports数组导入
Dynamic modules动态模块
优点是可以动态注册和配置提供程序
Middleware 中间件
定义:中间件是在路由处理程序之前调用的函数,中间件函数可以访问请求和相应对象,以及应用程序需请求响应周期中的nest()中间件函数。nest()中间件函数通常由名为next的变量表示
中间件函数可以执行以下任务:
- 执行任何代码
- 对请求和响应对象进行更改
- 结束请求-响应周期
- 调用堆栈中的下一个中间件函数
- 如果当前的中间件函数没有结束请求-响应周期,必须调用next()将控制传递给下一个中间件函数,否则 ,请求会被挂起。
中间件支持依赖注入
applying middleware 应用中间件
中间件不能在 @Module() 装饰器中列出。我们必须使用模块类的 configure() 方法来设置它们。包含中间件的模块必须实现 NestModule 接口。我们将 LoggerMiddleware 设置在 ApplicationModule 层上。
import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
}
}
下面是把包含路由路径的对象和请求方法 传递给forRoutes() 方法
中间件可以使用路由通配符
Middleware consumer中间件消费者
MiddlewareConsumer是一个帮助类,提供了几种内置的方法来管理中间件,可以被简单的连接起来
forRoutes() 可接受一个字符串、多个字符串、对象、一个控制器类甚至多个控制器类。在大多数情况下,您可能只会传递一个由逗号分隔的控制器列表。以下是单个控制器的示例:
这里的示例就是上面的应用中间件
exclude()是用来排斥某些路由的 :
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);
Functional middleware 函数式中间件
在前面使用到的loggerMiddleware类非常简单,没有成员,没有额外的方法,没有依赖关系
使用方法:
export function logger(req, res, next) {
console.log(`Request...`);
next();
};
consumer
.apply(logger)
.forRoutes(CatsController);
Multiple middleware 多个中间件
在apply方法内用逗号隔开
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
全局中间件
一次性把中间件绑定到每个注册路由,可以使用INESTapplication 实例提供的use()方法
Exception filters 异常过滤器
nest已经内置了异常层负责处理整个应用程序中所有抛出的异常,当捕获到未处理的异常是,用户能收到响应,所以这里主要是了解一下就行
Throwing standard exceptions 基础异常类
内置的HttpException类 从nestJs/common包中导入
使用方法:
@Get()
async findAll() {
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'This is a custom message',
}, HttpStatus.FORBIDDEN);
}
响应如下:
{
"status": 403,
"error": "This is a custom message"
}
Pipes 管道
管道是具有@Injectable() 装饰器的类,管道应实现 PipeTransform 接口。
两个类型:
转换: 管道把输入数据转化为所需的数据输出
验证: 对输入数据进行验证,如果验证成功继续传递,验证失败则抛出异常
管道参数会由控制器(controllers)的路由处理程序们进行处理,next会在调用这个方法之前插入一个管道,管道会先拦截方法的调用参数,进行转化或者验证处理没然后用转化好或者验证好的参数调用原来的方法。
注意: 管道在异常区域内运行,意味着当抛出异常时,核心异常处理程序和应用于当前上下文的异常过滤器处理 ,所以在pipe中发生异常,controller不会继续执行任何方法。
ValidationPipe 验证管道
ParseIntPipe 将字符串数字转整数
ParseFloatPipe 将字符串数字转浮点数
ParseBoolPipe 转布尔值
ParseArrayPipe 转化成数组类型
ParseUUIDPipe 解析字符串并验证是否为UUID
ParseEnumPipe 转化成枚举类型
DefaultValuePipe 设置参数默认值
ParseFilePipe 转化成文件类型
分析:管道的作用上面讲到转化和验证,
验证来说是接受一个值并立即放回相同的值,管道只是起到一个标记的作用
转化是把接受的值转化为目标值,内置的转换管道都有一个transform 函数 在里面有对应的转化逻辑。
Guards 守卫
守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate ****接口,守卫会根据运行是出现的某些条件(权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理,通常称之为授权。在express中一般由中间件处理授权。大部分的访问限制逻辑大多在中间件中,
但是缺点是:中间件不知道调用next()函数之后会执行哪处理程序,另外一方面,守卫可以访问ExecutionCOntext实例,因此确切的知道接下来执行什么,
守卫在每个中间件之后执行,但在任何拦截器或者管道之前执行
总结:nest中的登陆授权,应该使用守卫来做
Authorization guard 授权守卫
实现:假设用户是经过身份验证的 (请求头中有token)会提取和验证token 并使用提取的信息来确定请求是否继续进行
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
每个守卫必须实现一个canActivate()函数,此函数返回一个布尔值,只是是否允许当前请求。可以同步或者异步的接收返回相应(通过Promise或Observable) Nest使用返回值来控制下一个行为
true的话是用户处理;false的话nest将忽略当前的请求。
Execution context 执行上下文
canActivate()函数接受单个参数 ExecutionCOntext实例,ExecutionContext继承子ArgumentsHost ArgumentsHost 是传递给原始处理程序的参数的包装器,在上面的示例中,我们只是使用了之前在 ArgumentsHost上定义的帮助器方法来获得对请求对象的引用。有关此主题的更多信息。你可以在这里了解到更多(在异常过滤器章节)。
ExecutionContext 提供了更多功能,它扩展了 ArgumentsHost,但是也提供了有关当前执行过程的更多详细信息。
Role_based authentication 基于角色认证
只允许特定角色的用户访问,
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
Binding guards 绑定守卫
允许守卫控制范围(方法范围,全局范围)
Interceptors 拦截器
拦截器是是用@Injectable()装饰器注解的类。拦截器应该实现NestINterceptor接口
拦截器的功能是受到面向切面编程(AOP)技术的启发:
- 在函数执行之前、之后绑定额外的逻辑
- 转换从函数返回的结果
- 转化从函数抛出的异常
- 扩展基本函数行为
- 根据所选条件完全重写函数(缓存目的)
Basics 基础
每个拦截器都有intercept()方法,接受两个参数,第一个是ExecutionContext实例。继承自ArgumentsHost 是传递给原始处理程序的参数的一个包装,根据应用程序的类型包含不同的参数数组,
ArgumentsHost也是他的执行上下文。
call handler 调用处理程序
第二个参数是CallHandler handle()方法需要手动调用,如果不调用主处理程序根本不会执行
GraphQL
nest提供了两种构建GraphQL应用程序的方式, 模式优先和代码优先
在代码优先的模式中,仅仅使用装饰器和TYpeScript类来生成相应的 GraphQL schema
模式优先的方式: 本质是GraphQL SDL (模式定义语言),以一种与语言无关的方式,基本允许在不同的平台之间共享模式文件。
webscoket
在nest中 依赖注入,装饰器,异常过滤器,管道,守卫和拦截器, 都适用于网关,方便相同的组件可以跨基于http的平台,webscoket和微服务运行,
在 Nest 中,网关只是一个用 @WebSocketGateway() 装饰器注解的类。从技术上讲,网关与平台无关,这使得它们在创建适配器之后就可以与任何 WebSockets 库兼容。有两个开箱即用的WS平台:socket.io和ws。你可以选择最适合你需要的
步骤 先安装包:
$ npm i --save @nestjs/websockets @nestjs/platform-socket.io
然后更改监听端口:
@WebSocketGateway(80, { namespace: 'events' })
微服务
在nest中 微服务基本上是一个使用与http不同的传输层的应用程序
nest支持几种内置的传输层实现,称为传输器,负责在不同的微服务实例之间传输消息, 大多数传输器本质都支持请求-响应和基于事件的消息样式,nest在规范接口的后面抽象到每个传输器的实现细节,用于请求-响应和基于事件的消息传递。
微服务是通过tcp协议监听消息
转载自:https://juejin.cn/post/7231803877758271549