likes
comments
collection
share

NestJS核心概念

作者站长头像
站长
· 阅读数 61

介绍

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

NestJS核心概念

  • DTO: Data Transform Object 数据传输对象,常用于Controller接收用户传递的数据
/*
1. 约定了数据字段,属性
2. 方便数据校验
*/
class CreateUserDto {
 name: string
 age: number
}
  • DAO: Data Access Object 数据访问对象,是一层逻辑:包含Entity实体类, Repository的CRUD操作等。NestJS做了一层更高级的封装,通过ORM库与各种数据库对接,而这些ORM库就是DAO层。
@Entity()
export Class User {
 @PrimaryGeneratedColumn()
 id: number;
 @Column()
 name: string
 @Column()
 age: number
}

MVC:一种架构模式,英文全称 Model View Controller

  • Model: 模型代表一个存取数据的对象,它也可以带有逻辑,在数据变化时更新控制器

  • View: 视图代表模型包含的数据的可视化

  • Controller:控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开

NestJS核心概念

IOC/DI

  • 控制反转(Inversion Of Control)是一种面向对象编程中的设计原则,用来降低代码之间的耦合度。其基本思想是:借助于“第三方(容器)”实现具有依赖关系的对象之间的解耦。
  • 依赖注入(Dependency Injection)是一种用于实现IOC的设计模式,它允许在类的外部创建依赖对象,并通过不同的方式将这些对象提供给类。

DI 系统中存在两个主要角色:依赖使用者依赖提供者

各框架基本上都会使用一种称为 Injector(注入器, NestJS中也叫IOC Container) 的抽象来促进依赖消费者依赖提供者之间的互动。当有人请求依赖项时,注入器会检查其注册表以查看是否已有可用的实例。如果没有,就会创建一个新实例并将其存储在注册表中。

NestJS核心概念

AOP : 面向切面编程

AOP 是什么意思呢?什么是面向切面编程呢?

一个请求过来,会经过 Controller(控制器)、Service(服务)、Repository(数据库访问) 等层面。如果想在这个调用链路里加入一些通用逻辑该怎么加呢?比如日志记录、权限控制、异常处理等。

容易想到的是直接改造 Controller 层代码,加入这段逻辑。这样可以,但是不优雅,因为这些通用的逻辑侵入到了业务逻辑里面。能不能透明的给这些业务逻辑加上日志、权限等处理呢?

那是不是可以在调用 Controller 之前和之后加入一个执行通用逻辑的阶段呢?

比如这样:

NestJS核心概念

这样的横向扩展点就叫做切面,这种透明的加入一些切面逻辑的编程方式就叫做 AOP (面向切面编程)。

AOP 的好处是可以把一些通用逻辑分离到切面中,保持业务逻辑的存粹性,这样切面逻辑可以复用,还可以动态的增删

NestJS 的 MiddlewareGuardInterceptorPipeExceptionFileter 都是 AOP 思想的实现,只不过是不同位置的切面,它们都可以灵活的作用在某个路由或者全部路由,这就是 AOP 的优势。

核心概念

  • Controller:控制器,处理请求

  • Service: 服务,数据访问与核心逻辑

    • Repositories:处理在数据库中的数据
  • Module: 模块,组合所有的逻辑代码

  • Pipe: 管道,核验请求的数据,或对数据做转换

  • ExceptionFilter: 异常过滤器,处理请求时的错误

  • Guard: 守卫,鉴权与认证相关

  • Interceptors: 拦截器,分前置拦截和后置拦截,用于给请求和响应加入额外的逻辑

下面一张图介绍了以上核心概念在NestJS处理请求过程中所处的位置

NestJS核心概念

控制器(Controllers)详解

控制器负责处理传入的请求和向客户端返回响应

NestJS核心概念

控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。

@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 - servicerepositoryfactoryhelper 等等。 他们都可以通过 constructor 注入依赖关系。 这意味着对象可以彼此创建各种关系,并且“连接”对象实例的功能在很大程度上可以委托给 Nest运行时系统。 Provider 只是一个用 @Injectable() 装饰器注释的类。

NestJS核心概念

自定义提供者

@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,
  ) {}
}

NestJS核心概念

循环依赖

当两个类互相依赖时就会出现循环依赖. 例如,当 A类需要 B类,而 B类也需要 A类时,就会产生循环依赖Nest允许在提供者( provider )和模块( module )之间创建循环依赖关系.

NestJS核心概念

// 如果两个提供者之间互相依赖,可以通过注入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 指定的,不可访问。

NestJS核心概念

全局模块

如果通用模块A,会在很多其它模块中使用,则可以将模块A声明为全局模块,然后只在AppModule中引入即可,其它模块则可以直接使用A模块暴漏的Provider

NestJS核心概念

动态模块

如果要在模块导入时传入参数,则需要定义动态模块

@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
评论
请登录