(一)Nest.js 入门学习

初始化项目

npm i -g @nestjs/cli
nest new project-name

文件结构

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

(一)Nest.js 入门学习

运行程序

npm run start:dev # 可以使用热更新

基本概念

Controller

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

(一)Nest.js 入门学习

路由

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

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

nest g controller [name]
nest g controller user # 创建叫 user的 controller

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

(一)Nest.js 入门学习

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

import { Controller, Get } from '@nestjs/common';


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

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

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

路径参数

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

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,
    };
  }
}

也可以这样写

  @Get('id/:id')
  findUseById(@Param('id') id: number): Object {
    return {
      id: id,
    };
  }

请求对象

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

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

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…

(一)Nest.js 入门学习

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

  @Get('query')
  query(@Query() query: any): Object{
    return query;
  }

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

资源(不同的请求方式)

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

  @Post()
  create(): string {
    return 'This action adds a new person';
  }

路由通配符

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

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

 @Get('name*')
  everyOne(): string{
    return 'everyone'
  }

负载参数

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

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

export class userDto {
  name: string
  age: number
}

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

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

测试结果如下,成功响应了
(一)Nest.js 入门学习

启动和运行

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

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 类可以被视为提供者 – 服务、存储库、工厂、帮助程序等。提供程序的主要思想是它可以作为依赖项注入

(一)Nest.js 入门学习

使用命令创建一个 service

nest g service [name]
nest g service user // 创建 user.service

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

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

export interface User {
  name: string;
  age: number;
}

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

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

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

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

constructor (private userService: UserService) {}

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

  @Get()
  findAll(): Object {
    return this.userService.findAll();
  }

可选依赖

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

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() 装饰器。

import { Injectable, Inject } from '@nestjs/common';

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

Moudles

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

(一)Nest.js 入门学习

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

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

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

功能模块

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

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

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

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';


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

也可以使用命令

nest g module [name]
nest g module user

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

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

(一)Nest.js 入门学习

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

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

模块再导出

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

@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}

全局模块

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

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

动态模块

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

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 

{
  global: true,
  module: DatabaseModule,
  providers: providers,
  exports: providers,
}

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

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() 的方法调用:

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 {}

原文链接:https://juejin.cn/post/7358280964592468020 作者:佳豪

(0)
上一篇 2024年4月17日 上午10:37
下一篇 2024年4月17日 上午10:47

相关推荐

发表回复

登录后才能评论