(一)Nest.js 入门学习

初始化项目

bash 复制代码
npm i -g @nestjs/cli
nest new project-name

文件结构

  • app.module.ts:应用程序的根模块
  • app.controller:服务的消费者
  • app.service.ts:服务的提供者
  • app.controller.spec.ts:单元测试文件

运行程序

bash 复制代码
npm run start:dev # 可以使用热更新

基本概念

Controller

控制器负责处理传入请求并将响应返回给客户端,可以简单理解为控制器就是API接口,控制器的用途是接收应用程序的特定请求。

路由

路由机制控制哪个控制器接收哪些请求。通常,每个控制器都有多个路由,不同的路由可以执行不同的操作。例如:在 User 控制器当中,我们可以做用户相关的许多操作,这些操作我们可以进一步区分成各种路由,例如查询所有的用户、根据用户的 id 查询用户信息等等

我们可以使用如下命令快速创建一个 controller,例如要创建一个叫 user 的 controller

bash 复制代码
nest g controller [name]
nest g controller user # 创建叫 user的 controller

然后就会生成一个单元测试文件,和 controller

在 user.controller.ts中编写如下代码

ts 复制代码
import { Controller, Get } from '@nestjs/common';


@Controller('user')
export class UserController {
  @Get()
  findAll(): Object {
    return {
      name: "Tim",
      age: 18
    }
  }
}

不难看出,我们首先要自定义一个新的接口,在@Controller 装饰器中填写路径名称,在下方写上不同接口的请求方式和对应的方法

kotlin 复制代码
@Controller(路径)
export class xxx
    @请求方式
    函数: 返回类型

路径参数

如果我们还需要增加一个根据 id 查询的接口怎么办?,例如:user/id/1user/id/2

ts 复制代码
import { Controller, Get, Param } from '@nestjs/common';

@Controller('user')
export class UserController {
  @Get()
  findAll(): Object {
    return {
      name: 'Tim',
      age: 18,
    };
  }
  @Get('id/:id')
  findUseById(@Param() params: any): Object {
    return {
      id: params.id,
    };
  }
}

也可以这样写

ts 复制代码
  @Get('id/:id')
  findUseById(@Param('id') id: number): Object {
    return {
      id: id,
    };
  }

请求对象

我们如何实现获取请求体、查询参数等数据?

我们可以使用一个使用一个 Request 对象获取所有的请求的内容

ts 复制代码
import { Request } from 'express';
import { Controller, Get, Param, Req } from '@nestjs/common';

...
   @Get('request')
  getData(@Req() request: Request): Object {
    
    return {
      'body': request.body,
      'params': request.params,
      'query': request.query,
      'headers': request.headers,
      'cookies': request.cookies,
      'signedCookies': request.signedCookies,
      'method': request.method,
      'url': request.url,
      'path': request.path,
      'hostname':request.hostname,
    }
  }

当然,也可以使用特定的装饰器,获取对应的数据

docs.nestjs.com/controllers...

这里举个其他的例子,例如,访问http://localhost:3000/user/query?name=jack&&age=18,返回{"name": "jack","age": "18"}

ts 复制代码
  @Get('query')
  query(@Query() query: any): Object{
    return query;
  }

所以你可以根据不同的装饰器去获得你想要的数据,当然 all in reqeust 也可以,只是可能有些不优雅

资源(不同的请求方式)

就是这么简单。Nest 为所有标准 HTTP 方法提供了修饰器: @Get()@Post()@Put()@Delete()@Patch() @Options()@Head() 。此外, @All() 还定义了一个处理所有这些问题的办法。

ts 复制代码
  @Post()
  create(): string {
    return 'This action adds a new person';
  }

路由通配符

nestjs还支持基于模式的路由。例如,星号用作通配符,将匹配任何字符组合

访问http://localhost:3000/user/name123,不管 name 之后都是什么都会进入这个路由

ts 复制代码
 @Get('name*')
  everyOne(): string{
    return 'everyone'
  }

负载参数

定义一个 userDto,Dto 类似于 ts 中的 interface,但是为什么不使用 ts 呢,以下是官方的回答

DTO 是一个对象,用于定义如何通过网络发送数据。我们可以通过使用 TypeScript 接口或简单的类来确定 DTO 模式。有趣的是,我们建议在此处使用类。为什么?类是 JavaScript ES6 标准的一部分,因此它们在编译的 JavaScript 中保留为真实实体。另一方面,由于 TypeScript 接口在转译过程中被删除,因此 Nest 无法在运行时引用它们。这很重要,因为当 Pipes 等功能在运行时可以访问变量的元类型时,它们会实现其他可能性。

ts 复制代码
export class userDto {
  name: string
  age: number
}

使用 Body 装饰器来接收这个请求参数

ts 复制代码
  @Post('new')
  createUser(@Body() user: userDto): string {
    return `created user ${user.name}`;
  }

测试结果如下,成功响应了

启动和运行

控制器始终属于一个模块,这就是为什么我们在装饰器中 @Module() 包含 controllers 的原因

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserController } from './user/user.controller';

@Module({
  imports: [],
  controllers: [AppController, UserController],
  providers: [AppService],
})
export class AppModule {}

我们使用 @Module() 装饰器将元数据附加到模块类,Nest 现在可以轻松反映必须挂载哪些控制器

Providers

提供程序是 Nest 中的一个基本概念。许多基本的 Nest 类可以被视为提供者 - 服务、存储库、工厂、帮助程序等。提供程序的主要思想是它可以作为依赖项注入

使用命令创建一个 service

bash 复制代码
nest g service [name]
nest g service user // 创建 user.service

接下来就在这个 service 中模拟一个数据的检索和存储

因为要模拟一个检索和存储,需要先定义一下这个模拟数据的结构

ts 复制代码
export interface User {
  name: string;
  age: number;
}

然后在 service 中写好创建和查询的方法,你可以注意到有个@Injectable()的装饰器,使用了这个装饰器,这个依赖就会被 nestjs 的 IOC 容器进行管理,如果你学过 springboot,那么 IOC 的概念是差不多的,就是把实例化的控制权交给了框架,通过IOC去管理实例的生命周期,包括实例化、创建、使用、依赖、销毁等

如果对 IOC 感兴趣可以看看文章: en.wikipedia.org/wiki/Invers...

修改 service 为如下的代码,模拟检索和存储

ts 复制代码
import { Injectable } from '@nestjs/common';
import { User } from './interface/user.interface'

@Injectable()
export class UserService {
  private readonly user: User[] = []
  
  create(user: User){
    this.user.push(user)
  }

  findAll(): User[]{
    return this.user
  }
}

如何注入依赖并使用?

我们说了 provider 是一个提供服务能力的东西,我们使用类去定义了他可以提供的功能,然后 controller 中就可以使用这些功能

在 controller 中创建一个构造器,该构造器接收一个我们刚刚定义的 service

ts 复制代码
constructor (private userService: UserService) {}

service 是使用了 IOC 容器进行管理的,我们无需考虑去实例化他,可以直接使用

ts 复制代码
  @Get()
  findAll(): Object {
    return this.userService.findAll();
  }

可选依赖

有时,可能存在不一定必须解析的依赖项。例如,某个类可能依赖于配置对象,但如果未传递任何配置对象,则应使用默认值。在这种情况下,依赖项变为可选,因为缺少配置提供程序不会导致错误。

ts 复制代码
import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

它接受一个通过 Inject('HTTP_OPTIONS') 装饰器注入的参数,这个参数通常是一个配置对象,它包含了创建 HTTP 客户端所需的所有选项。@Optional() 装饰器表示这个参数是可选的,如果在依赖注入过程中没有找到对应的提供者,httpClient 将被设置为 undefined

  • @Inject('HTTP_OPTIONS') 表示 NestJS 将尝试解析名为 HTTP_OPTIONS 的提供者,并将其注入到 httpClient 属性中。
  • private httpClient: T 定义了一个私有属性 httpClient,它的类型是泛型参数 T,这个属性将被用来存储注入的 HTTP 客户端实例。

基于属性的依赖注入

到目前为止,我们使用的技术称为基于构造函数的注入,因为提供程序是通过构造函数方法注入的。在一些非常特殊的情况下,基于属性的注入可能是有用的。例如,如果顶级类依赖于一个或多个提供程序,则通过从构造函数调用 super() 子类来一直传递它们可能会非常繁琐。为了避免这种情况,可以在属性级别使用 @Inject() 装饰器。

ts 复制代码
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

Moudles

模块是用 @Module() 装饰器注释的类。 @Module() 装饰器提供 Nest 用来组织应用程序结构的元数据。

每个应用程序至少有一个模块,即根模块。根模块是 Nest 用来构建应用程序图的起点,是 Nest 用来解析模块和提供程序关系和依赖关系的内部数据结构。强烈建议将模块作为组织组件的有效方法。因此,对于大多数应用程序,生成的架构将采用多个模块,每个模块都封装了一组密切相关的功能。

@Module() 装饰器采用单个对象,其属性描述模块:

  • providers: 将由 Nest 注入器实例化的提供程序,并且至少可以在此模块之间共享
  • controllers: 此模块中定义的必须实例化的控制器集
  • imports:导出此模块中所需的提供程序的导入模块列表
  • exports:providers 该模块的子集由此模块提供,并且应该在导入此模块的其他模块中可用

功能模块

我们之前使用命令创建模块的时候,实际上,它帮我们在 app.module.ts 配置好了,所以我们才可以进行访问到 user 里面的内容

ts 复制代码
@Module({
  imports: [],
  controllers: [AppController, UserController],
  providers: [AppService, UserService],
})

那么为了更好地去管理不同的模块,每个模块都应该有一个 module 所以我们进行抽离

ts 复制代码
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';


@Module({
  imports: [],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

也可以使用命令

ts 复制代码
nest g module [name]
nest g module user

我们需要做的最后一件事是将这个模块导入到根模块( app.module.ts 文件中定义的 AppModule

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';

@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

共享模块

在 Nest 中,模块默认是单例,因此可以毫不费力地在多个模块之间共享任何提供程序的同一实例。

很简单,我们只需要在 module 中的 exports 中导出即可,任何导入的 UserModule 模块都可以访问 UserService 并且将与导入它的所有其他模块共享同一个实例。

ts 复制代码
@Module({
  imports: [],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})

模块再导出

可以将一个模块导入后,又进行一次导出

ts 复制代码
@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}

全局模块

当您想要提供一组开箱即用的提供程序(例如,帮助程序、数据库连接等)时,请使用 @Global() 装饰器使模块全局化。

python 复制代码
import { Module, Global } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';


@Module({
  imports: [],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})
export class UserModule {}

@Global() 装饰器使模块具有全局范围。全局模块只能注册一次,通常由根模块或核心模块注册。UserModule被标记为全局模块。这意味着一旦它在任何地方被导入一次,UserService就可以在整个应用程序中被注入和使用,无需在每个模块中再次导入UserModule

动态模块

这些模块可以动态注册和配置提供程序

ts 复制代码
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';

@Module({
  providers: [Connection],
  exports: [Connection],
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}

forRoot() 方法可以同步或异步(即通过 Promise )返回动态模块。

代码解释:

  1. 导入必要的依赖

    • ModuleDynamicModule是从@nestjs/common包中导入的装饰器,用于定义NestJS模块。
    • createDatabaseProviders是一个函数,用于创建数据库提供者。这个函数可能是自定义的,用于生成数据库连接所需的提供者。
    • Connection是从./connection.provider文件中导入的类,可能是用于创建和管理数据库连接的提供者。
  2. 定义DatabaseModule

    • @Module()装饰器用于定义NestJS模块。这里,它接受一个对象,包含providersexports属性。
    • providers: [Connection]表示这个模块提供Connection服务。
    • exports: [Connection]表示这个模块导出Connection服务,使其可以在其他模块中被注入和使用。
  3. 静态方法forRoot

    • forRoot是一个静态方法,用于动态创建模块。它接受两个参数:entities(实体数组)和options(配置选项)。
    • const providers = createDatabaseProviders(options, entities);调用createDatabaseProviders函数,根据提供的选项和实体创建数据库提供者。
    • 方法返回一个DynamicModule对象,其中包含模块的定义、提供者和导出的提供者。

如果要在全局作用域中注册动态模块,请将 global 该属性设置为 true

ts 复制代码
{
  global: true,
  module: DatabaseModule,
  providers: providers,
  exports: providers,
}

DatabaseModule 可以按以下方式导入和配置:

ts 复制代码
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';

@Module({
  imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}

如果要反过来重新导出动态模块,可以省略 exports 数组中 forRoot() 的方法调用:

ts 复制代码
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';

@Module({
  imports: [DatabaseModule.forRoot([User])],
  exports: [DatabaseModule],
})
export class AppModule {}
相关推荐
Martin -Tang23 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发24 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端