NestJS核心概念
介绍
Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架。
- 内置支持TS
- 大量使用装饰器
- 编程风格:结合了OOP(面向对象编程)、FP(函数式编程)、FRP(函数响应式编程)
- 渐进式:以模块为编程核心概念,独立依赖包引用
- 文档完善:提供了各种开发场景支持
- 对开发者友好:内置CLI工具快速生成基础代码
- …还有更多
在底层,Nest使用强大的 HTTP Server 框架,如 Express(默认)和 Fastify。Nest 在这些框架之上提供了一定程度的抽象,同时也将其 API 直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。
安装
使用 Nest CLI 构建项目,请运行以下命令。这将创建一个新的项目目录,并生成 Nest 核心文件和支持模块,为您的项目创建传统的基础结构。
# 安装cli
npm i -g @nestjs/cli
# 创建nest工程
nest new project-name
# 启动并访问http://localhost:3000
pnpm start
前置知识
在介绍NestJS的核心概念之前要先了解下后端开发过程中常见的一些术语
DTO
/DAO
- DTO:
D
ataT
ransformO
bject 数据传输对象,常用于Controller
接收用户传递的数据
/*
1. 约定了数据字段,属性
2. 方便数据校验
*/
class CreateUserDto {
name: string
age: number
}
- DAO:
D
ataA
ccessO
bject 数据访问对象,是一层逻辑:包含Entity
实体类,Repository
的CRUD操作等。NestJS做了一层更高级的封装,通过ORM库与各种数据库对接,而这些ORM库就是DAO层。
@Entity()
export Class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string
@Column()
age: number
}
MVC
:一种架构模式,英文全称 M
odel V
iew C
ontroller
-
Model: 模型代表一个存取数据的对象,它也可以带有逻辑,在数据变化时更新控制器
-
View: 视图代表模型包含的数据的可视化
-
Controller:控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开
IOC
/DI
- 控制反转(
I
nversionO
fC
ontrol)是一种面向对象编程中的设计原则,用来降低代码之间的耦合度。其基本思想是:借助于“第三方(容器)”实现具有依赖关系的对象之间的解耦。 - 依赖注入(
D
ependencyI
njection)是一种用于实现IOC的设计模式,它允许在类的外部创建依赖对象,并通过不同的方式将这些对象提供给类。
DI 系统中存在两个主要角色:依赖使用者和依赖提供者
各框架基本上都会使用一种称为 Injector
(注入器, NestJS中也叫IOC Container
) 的抽象来促进依赖消费者和依赖提供者之间的互动。当有人请求依赖项时,注入器会检查其注册表以查看是否已有可用的实例。如果没有,就会创建一个新实例并将其存储在注册表中。
AOP
: 面向切面编程
AOP 是什么意思呢?什么是面向切面编程呢?
一个请求过来,会经过 Controller
(控制器)、Service
(服务)、Repository
(数据库访问) 等层面。如果想在这个调用链路里加入一些通用逻辑该怎么加呢?比如日志记录、权限控制、异常处理等。
容易想到的是直接改造 Controller
层代码,加入这段逻辑。这样可以,但是不优雅,因为这些通用的逻辑侵入到了业务逻辑里面。能不能透明的给这些业务逻辑加上日志、权限等处理呢?
那是不是可以在调用 Controller
之前和之后加入一个执行通用逻辑的阶段呢?
比如这样:
这样的横向扩展点就叫做切面,这种透明的加入一些切面逻辑的编程方式就叫做 AOP (面向切面编程)。
AOP 的好处是可以把一些通用逻辑分离到切面中,保持业务逻辑的存粹性,这样切面逻辑可以复用,还可以动态的增删
NestJS 的 Middleware
、Guard
、Interceptor
、Pipe
、ExceptionFileter
都是 AOP 思想的实现,只不过是不同位置的切面,它们都可以灵活的作用在某个路由或者全部路由,这就是 AOP 的优势。
核心概念
-
Controller:控制器,处理请求
-
Service: 服务,数据访问与核心逻辑
- Repositories:处理在数据库中的数据
-
Module: 模块,组合所有的逻辑代码
-
Pipe: 管道,核验请求的数据,或对数据做转换
-
ExceptionFilter: 异常过滤器,处理请求时的错误
-
Guard: 守卫,鉴权与认证相关
-
Interceptors: 拦截器,分前置拦截和后置拦截,用于给请求和响应加入额外的逻辑
下面一张图介绍了以上核心概念在NestJS处理请求过程中所处的位置
控制器(Controllers)详解
控制器负责处理传入的请求和向客户端返回响应。
控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。
@Controller('example')
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
@Post()
create(@Body() createExampleDto: CreateExampleDto) {
return this.exampleService.create(createExampleDto);
}
@Get()
findAll() {
return this.exampleService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.exampleService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateExampleDto: UpdateExampleDto) {
return this.exampleService.update(+id, updateExampleDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.exampleService.remove(+id);
}
}
控制器的请求方法如下
@Get
用于获取数据@Post
用于新增数据@Patch
用于更新部分数据@Put
用于更新全部数据@Delete
用于删除数据@Options
用于对cors的跨域预检(一般用不到)@Head
用于自定义请求头,常用于下载,导出excel文件等
请求对象获取
可以通过以下装饰器获取需要的请求对象
@Request(), @Req()
: 请求数据
@Response(), @Res()
: 响应数据
@Next()
: 执行下一个中间件(一般用不到)
@Session()
: session对象(一般用不到)
@Param(key?: string)
:获取url中的params参数,比如 posts/1
@Body(key?: string)
:请求主体数据,一般结合DTO使用,用于新增或修改数据
@Query(key?: string)
:获取url中的查询数据,比如posts?page=1 @HttpCode(statusCode: number)
:指定响应码,比如@HttpCode(200) @Redirect(url:string,statusCode:number)
:跳转到其它url,比如@Redirect('/ccc',301)
提供者(Providers)详解
Providers 是 Nest
的一个基本概念。许多基本的 Nest
类可能被视为 provider - service
, repository
, factory
, helper
等等。 他们都可以通过 constructor
注入依赖关系。 这意味着对象可以彼此创建各种关系,并且“连接”对象实例的功能在很大程度上可以委托给 Nest
运行时系统。 Provider 只是一个用 @Injectable()
装饰器注释的类。
自定义提供者
@Module({
controllers: [ExampleController],
providers: [
ExampleService,
{
// 标准Provider
provide: StandardProvider,
useClass: StandardProvider,
},
{
// 值Provider
provide: ValueProvider,
useValue: new ValueProvider(1),
},
{
// 非类提供者
provide: 'non-class-provider',
useClass: NonClassProvider,
},
{
// 类Provider
provide: ClassProvider,
useClass:
process.env.NODE_ENV === 'development'
? DevClassProvider
: ProdClassProvider,
},
{
// 工厂Provider
provide: FactoryProvider,
useFactory: (exampleService: ExampleService) => {
const result = exampleService.findAll();
return new FactoryProvider(result);
},
inject: [ExampleService],
},
{
// 别名提供者
// useExisting 语法允许您为现有的Provider建别名
provide: 'AliasedStandardProvider',
useExisting: StandardProvider,
},
],
})
export class ExampleModule {}
@Controller('example')
export class ExampleController {
constructor(
private readonly exampleService: ExampleService,
private readonly standardProvider: StandardProvider,
private readonly valueProvider: ValueProvider,
@Inject('non-class-provider')
private readonly nonClassProvider: NonClassProvider,
private readonly classProvider: ClassProvider,
private readonly factoryProvider: FactoryProvider,
@Inject('AliasedStandardProvider')
private readonly aliasStandardProvider: StandardProvider,
) {}
}
循环依赖
当两个类互相依赖时就会出现循环依赖. 例如,当 A
类需要 B
类,而 B
类也需要 A
类时,就会产生循环依赖。Nest
允许在提供者( provider
)和模块( module
)之间创建循环依赖关系.
// 如果两个提供者之间互相依赖,可以通过注入forwardRef来实现
@Injectable()
export class CircleAProvider {
constructor(
@Inject(forwardRef(() => CircleBProvider))
private readonly circleBProvider: CircleBProvider,
) {}
run() {
console.log('循环依赖 A run');
}
}
@Injectable()
export class CircleBProvider {
constructor(
@Inject(forwardRef(() => CircleAProvider))
private readonly circleAProvider: CircleAProvider,
) {}
run() {
console.log('循环依赖 B run');
}
}
// 同时模块间也可以循环依赖,例如
@Module({
imports: [forwardRef(() => CatsModule)],
})
export class CommonModule {}
Provider 作用域
- DEFAULT: 默认的单例注入,每次请求都是使用同一个实例,项目启动时创建该单例
- REQUEST: 每次请求创建一个新的提供者实例,并且该实例在这次请求内被所有调用者所共享,请求完毕自动销毁
- TRANSIENT:每次被使用者注入该提供者时都会创建一个新的实例,换新的请求后就不变了
为了切换到另一个注入范围,您必须向 @Injectable()
装饰器传递一个选项对象。
@Injectable({scope: Scope.TRANSIENT})
export class ScopeProvider_Transient{
constructor() {
console.log(' Transient ScopeProvider constructor');
}
}
在自定义提供者的情况下,您必须设置一个额外的范围属性。
{
provide: ScopeProvider_Request,
useClass: ScopeProvider_Request,
scope: Scope.REQUEST
}
模块(Module)详解
模块是具有 @Module()
装饰器的类。 @Module()
装饰器提供了元数据,Nest 用它来组织应用程序结构。
@module()
装饰器接受一个描述模块属性的对象:
providers | 由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享 |
---|---|
controllers | 必须创建的一组控制器 |
imports | 导入模块的列表,这些模块导出了此模块中所需提供者 |
exports | 由本模块提供并应在其他模块中可用的提供者的子集。 |
共享模块
当我们通过 imports
属性引入一个模块 A 时,我们需要通过 A 模块的 exports
属性来了解其暴露的功能(provider
),其未在 exports
指定的,不可访问。
全局模块
如果通用模块A,会在很多其它模块中使用,则可以将模块A声明为全局模块,然后只在AppModule中引入即可,其它模块则可以直接使用A模块暴漏的Provider
动态模块
如果要在模块导入时传入参数,则需要定义动态模块
@Module({
providers: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
@Module({
imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}
转载自:https://juejin.cn/post/7210048692622409786