介绍说明
- Nest.js 是一个 Node.js 的后端框架,它对 express 等 http 平台做了一层封装,解决了架构问题。它提供了 express 没有的 MVC、IOC、AOP 等架构特性,使得代码更容易维护、扩展。
- nest 内实现
AOP 方式有5种方式:包括 Middleware、Guard、Inteceptor、Pipe、ExceptionFilter
- 执行顺序:
Middleware -> Guard -> Inteceptor(前) -> Pipe -> Inteceptor(后) -> ExceptionFilter
- Nestjs 依赖注入(DI)思想是尽可能使用类而不是实例,这样 Nest 就可以在整个模块中重用同一类的实例
- 依赖注入: 是控制反转思想的一种实现,它将依赖的实例交给
Ioc容器去执行。这里的Ioc容器就是@Module()
- 在nest内基本的实现分为3个步骤
- 在
cats.service.ts中使用@Injectable()装饰器声明CatsService类是一个可被Ioc容器管理的类
- 在
cats.controller.ts中的CatsController声明一个依赖于CatsService令牌的注入值
constructor(private readonly catsService(这是个 value): CatsService(这是个 token) )
- 在
app.module.ts中,将标记CatsServicetoken与cats.service.ts文件中CatsService类的关联关系
import{ CatsService } from './cats/cats.service'
@Module({ provides: [ CatsService ] }) -> { prviders: [{ provide: CatsService-token, useClass: CatsService-class }] }
- 只后当
Ioc容器实例化CatsController时。执行constructor()就会查找所有的依赖项,并通过@Module内配置的依赖关系去找对应的Provider并实例化(可缓存的)。
- 并且这些依赖之间是可以相互依赖的
其他说明
- 全局API
- app.useGlobalGuards/useGlobalInterceptors/useGlobalPipes/useGlobalFilters() 添加的处理函数执行顺序按照添加的顺序执行
- app.enableCors({ options }) 允许跨域
- req/res.app 可获取全局实例对象
Controller
- 定义路由
@Controller(xxx)可设置前缀。
@HttpCode(304)设置响应的状态码
@Header(key, value)设置响应的头信息
@Redirect(url, statusCode)重定向,可配合路由处理方法控制重定向的URL
- 请求方式
@Get/Post/Patch/Delete/Put/Head/Options/All(xxx)路由支持使用通配符
- 使用
:id设置param路由参数
- 参数获取方式
@Param/Body/Query/Headers(key?) xxx
- 未配置
key时,整体赋值给xxx
- 配置
key时,把匹配到的具体值给xxx
- 其他信息获取
@Req/Res/Nest/Session/Ip/HostParam()
@Req()可以获取完整的req对象
@Res()可获取res对象。可以使用res.get/json/send/sendFile()等
- 注意:nestjs 不建议直接使用原生的
res.json/send() 这类的响应式方式。会让部分拦截器/装饰器失效,但可以通过配置@Res({ passthrough: true })解决
req/res.xxx是可以获取express上所有原生方法
- 定义
DTO用于配合body参数的获取,并可配合class-validator进行参数校验
- dto 直接可通过
PartialType/PickType/OmitType/IntersectionType控制继承内容
PartialType 将输入类型都改为可选类型
PickType(createDto, [...args] as const) {}从类中选择属性继承
OmitType(createDto, [...args] as const) {}从类中排除属性继承
IntersectionType(xxxDto, xxxDto)从最多4个类继承
- 其他类型参数也可用
class-validator配合校验。使用方式和dto一样
import { IsNumberString } from 'class-validator';
class OneParams {
@IsNumberString()
id: number;
}
@Get(':id')
findOne(@Param() params: OneParams){}
Providers 提供者
- 对应着
service服务,通过@Injectable()装饰
- 依赖注入
constructor(private readonly catsService: CatsService){}把模块提供给constructor使当前类下可以访问到内容
Modules 模块
- 用于关联依赖并传递给IOC容器
- 模块可以导出自身的
controller/provider还可以导出自己exports: [xxxModule]
- 使用
@Global()创建全局模块,这样该模块只要在根模块注册。其他模块中都可访问到
Middlewares 中间件
- 模块中间件需要实现
NestModule接口并设置configure方法
- 全局中间件
app.use(logger);
- **重要:**这个中间件的逻辑和
express内的不相同,按请求的生命周期顺序中间件只会进入一次且也不是函数嵌套的逻辑。所以同样的处理需要使用nest的拦截器方式
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(logger)
.exclude({ path: 'api/user/:id', method: RequestMethod.GET })
.forRoutes(UserController);
}
}
export function logger(req: Request, res: Response, next: NextFunction) {
console.log('-- logger --', req.path);
next();
}
ExceptionFilters 异常过滤器
- 通过
ExceptionFilter<T>接口实现
- 方法范围
@UseFilters(HttpExceptionFilter)可在Controller内某个路由下添加过滤器
- 控制器范围
@UseFilters(HttpExceptionFilter) -> export class XxxController {}
- 全局范围
app.useGlobalFilters(new HttpExceptionFilter())
@Catch()
export class AnyExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
console.log('### Any exception filter ###', JSON.stringify(exception));
const ctx = host.switchToHttp();
const req = ctx.getRequest<Request>();
const res = ctx.getResponse<Response>();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const resBody: ErrorResponse = {
errno: -1,
message: exception.message || '服务错误',
path: req.url,
};
res.status(status).json(resBody);
}
}
Pipes 管道
- 用于输入数据的转化、数据的有效性校验
- 使用
@Injectable()装饰器注解改类,并基于PipeTransform<T>接口实现transform()方法
- 内置转化
ParseIntPipe/ParseFloatPipe/ParseBoolPipe/ParseEnumPipe/ParseArrayPipe/DefaultValuePipe配合参数获取装饰器使用@Param/Query/Body()
Guards 守卫
- 用于判断是否满足某些定义的条件,通过后直到到路由程序
- 使用
@Injectable()装饰器注解改类,并基于CanActivate<T>接口实现canActivate()方法。通过return true/false控制
- 返回 false 是触发
throw new UnauthorizedException()
- 可以在
constructor() {}内注入相关依赖
- 使用
@UseGuards()装饰器使用守卫,同样支持路由方法/控制器/全局等
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
return true;
}
}
Interceptors 拦截器
- 可以在路由函数的前/后两处执行额外的逻辑。有些类似
express中间件的逻辑
- 使用
@Injectable()装饰器注解改类,并基于NestInterceptor<T>接口实现intercept()方法
- 可以绑定在全局/类/路由函数等
@UseInterceptors()
- 可以在
constructor() {}内注入相关依赖
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest();
return (
next
.handle()
.pipe(
map((data = {}) => {
const resData: SuccessResponse = {
errno: 0,
message: '请求成功',
data,
};
return resData;
}),
)
);
}
}
自定义decorator/provider
- provider
- useValue/useClass/useFactory(支持 async/await)
- useFactory在
@Module时就会先执行,之后如果在this.xxx读取还会再次执行
- useFactory可配置
inject用于配置参数
- 所有
token为string都需要使用@Inject('xxx')装饰器来提供注入
- 使用
@Inject('CONFIG') [private] [readonly] configFactory: aFn方式
- 注意: 有两种使用方式
constructor(@Inject('Connection') private connection: string){}
create(){
this.connection
}
constructor(@Inject('Connection') connection: string){}
create(){
connection
}
- 动态模块
- 主要是为了在导入其他模块c时可传递一些参数,实际使用的还是a模块内的
controller/service
- 使用
@Module({})装饰器声明一个模块类,然后内部实现register(options)函数并返回模块对象
- 参数通过
useValue形式提供,并配合@Inject('xxx') options来获取
- 之后在需要倒入其他模块
@Module({ imports: [ xxxModule.register( {params} ) ] })来传递自己想要的参数内容
- 作用域:可在
Controller/Injectable装饰器内设置作用域范围
- scope.DEFAULT 同引用生命周期
- scope.REQUEST 请求处理完后销毁
- scope.TRANSIENT 每个使用Provider的程序都会得到一个独立的实例
可执行上下文 excutionContext
- 基础类
ArgumentsHost允许选择可执行的上下文对象
- getType/switchToRpc/switchToHttp/switchToWs()
- switchToHttp() 可返回
request/response/next
ExecutionContext继承至ArgumentsHost
- 拓展了getClass/getHandler() 返回当前处理的
class/函数
Reflector元数据访问类
- 返回 get/getAllAndOverride/getAllAndMerge(key, 当前处理 class/function)
- 注意:guard/interface/filter/decorator 等都能拿到可执行上下文
session/cookie 使用
- redis-session
- redis+session 添加的方式和 express 里一样
- 使用可通过两种方式
@Req() -> req.session.xxx或者@Session() -> session.xxx
- 失效方式
session=null或store.destroy(sid, callback)或者从redis里直接删除
- cookie
- 添加
cookie-parser包用于解析。然后使用res.cookie(key, value [, options])并配置@Res({ passthrough: truee })
- 建议创建时配置密钥
cookieParser('_xxx_'),让之后设置{ signed: true }时报错
- cookie 内容获取有
req.cookies/signedCookies两种方式
Sequelize ORM
- 主要内容查看 链接
- 原来关联后附带的方法重命名了
- Modulex.$set/add/remove/create('key', [ instance ])
- x.$get/count/has('key')
验证配合 class-validator
- 其实 nest 内部已经提供了很不错的验证,引入
class-validator是为了让dto类也支持校验
- 内部
ValidationPipe的部分配置参数
- transform 将传入的 js 对象按 dto 配置的进行类型转化
- whitelist 会删除未配置 validator 的属性
- disableErrorMessages 验证信息不返回给客户端
- exceptionFactory 配置异常处理函数
- forbidNonWhitelisted 当有未配置校验的属性时报错。需要同时配置
whitelist: true
- skipMissingProperties 跳过对缺失属性的校验。当开启式可配合
@IsDefined()对想要的缺失属性进行强校验
- validationError.target 是否显示传入的完整源数据
- validationError.value 是否显示错误具体的 value
- 显示手动转化方式
- 像
param/query方式传入的都是string类型的值,可通过以下方式进行手动转化
- 通过
@Param('id', ParseIntPipe) id: number方式使用。所有请求参数获取方式都支持pipe
- 导出
ParseIntPipe/ParseFloatPipe/ParseBoolPipe/ParseEnumPipe/ParseArrayPipe/DefaultValuePipe
- 解析和验证数组
@Post()
createBulk(
@Body(new ParseArrayPipe({ items: CreateUserDto }))
createUserDtos: CreateUserDto[],
) {
return 'This action adds new users';
}
@Get()
findByIds(
@Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
ids: number[],
) {
return 'This action returns users by ids';
}
- class-validator 文档部分摘录
- 自定义错误信息
@xxx({ message: string|function })
- Array/Set/Map 项每个都校验
@xxx({ each: true })
- 校验继承性:如果从一个已经有验证规则的
dto类继承,并对同个属性添加新的校验。那么这个属性包含两个类的验证规则
- 嵌套校验
@ValidateNested()对应对象必须也是一个配置了校验规则的类
- promise 对象校验
@ValidatePromise()
- 条件校验
@ValidateIf(o => o.otherProperty === 'value')只有返回true才触发校验。o可获取类内的其他属性
- 常用校验装饰器
- @IsInt/IsBoolean/IsString()
- @Length(5, 10)/MinLength/MaxLength(5) 约束
string 长度
- @IsArray/ArrayNotEmpty/IsObject/IsNotEmptyObject() 数组/对象
- @IsIn/IsNotIn(values: any[])
array 是否包含指定值
- @Contains/NotContains( value ) 内容是否包含指定值
- @IsNumber()
- @Min/Max(10) 检验
number 是否满足给定的值
- @IsEnum( 枚举定义 ) 固定的枚举类型
- @IsDate() 是否是
date类型
- @MinDate/MaxDate( 日期值 ) 在日期范围内
- @IsEmail()
- @IsUrl() 是否满是URL
- @IsAlpha() 只包含
a-zA-Z
- @IsAlphanumeric 只包含字母数字
- @IsBooleanString/IsDateString/IsNumberString() 是否是指定类型的
string值
- @IsBase32/IsBase64/IsDataURI() 指定的数据类型
- @Matches(/abc/, i) 正则匹配