从0到1深入浅出构建Nest.Js项目

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用JavaScript 的渐进增强的能力,使用并完全支持 TypeScript (仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。

在底层,Nest 构建在强大的 HTTP 服务器框架上,例如 Express (默认),并且还可以通过配置从而使用 Fastify

Nest 在这些常见的 Node.js 框架 (Express/Fastify) 之上提高了一个抽象级别,但仍然向开发者直接暴露了底层框架的 API。这使得开发者可以自由地使用适用于底层平台的无数的第三方模块。

NestJS 是一个基于 TypeScript 的服务器端应用程序框架。 它提供了一组丰富的工具和模块 来帮助开发人员构建高效、可扩展的服务器端应用程序。这些元素可以通过依赖注入系统 自动注册,并可以在整个应用程序中共享

总的来说,NestJS 是一个功能丰富、易于使用的服务器端应用程序框架,可帮助开发人员快速构建高效、可扩展的服务器端应用程序。

一、NestJS优势

NestJS 的一些优势包括:

  • 构建在现代 JavaScript 栈之上,因此使用了最新的 JavaScript 技术。
  • 基于 Angular 的架构和语法,提供了强大的模块化系统和依赖注入功能。
  • 基于 TypeScript,提供了强类型和静态类型检查。
  • 提供了丰富的工具和模块,可用于构建各种类型的服务器端应用程序,包括 RESTful API、GraphQL API、WebSocket 服务器等。
  • 提供了一组可扩展的构建块,可用于快速构建应用程序。
  • 提供了与主流数据库和身份验证系统的集成。

二、IOC(控制反转 )和依赖注入DI概念

这两个概念不要搞混了,IOC其实是面向对象编程中的一种设计模式,而DI则是为了实现IOC的一种技术。

下面简单认识一下为什么需要IOC,IOC有什么好处,简单来说就是减少了固定性,通过外部传参进行控制内部本身固定的一些变量,如下例子:

在我们的代码中,经常会出现一个类依赖于另外一个类的情况,比如这样:

typescript 复制代码
class Dog {}

class Person {
  private _pet

  constructor () {
    this._pet = new Dog()
  }
}

const xiaoming = new Person()

当我们遇到类与类之间存在依赖关系时,一般会直接在类的内部创建依赖对象,这样就会导致各个类之间形成耦合,并且这种关系会随着依赖关系越来越复杂从而耦合度也会越来越高,最终造成代码的难以维护。

在上述例子中:

  • Person类固定依赖于Dog类,如果后续Person想要依赖于其他宠物类,是无法轻易修改的。
  • 并且如果Dog类有所变化,比如其属性颜色染成了黑色,Person类也会直接受到影响。

IOC的思想就是将类的依赖动态注入,以解决上述两个问题:

javascript 复制代码
class Dog {}

class Person {
  private _pet

  constructor (pet) {
    this._pet = pet
  }
}

const xiaohei = new Dog()
const xiaoming = new Person(xiaohei) // 将实例化的 dog 传入 person 类

这样,我们就实现了类的控制反转,同时,我们需要有一个容器来维护各个对象实例,当用户需要使用实例时,容器会自动将对象实例化给用户,这部分通常由框架处理。

这种动态注入的思想叫做依赖注入 (DI, Dependency Injection),它是 IoC 的一种应用形式,把对象或依赖的实例化交给IOC容器去处理,在NestJS中这个容器就是NestJS的运行时系统。当需要一个对象实例的时候,我们不需要自己手动new xxxClass(),只需要在合适的地方对类进行注册,在需要用到的地方直接注入,容器将为我们完成new的动作。

三、NestJs中使用方式

Nest中使用依赖注入一般有以下三步:

1、声明定义

使用@Injectable装饰器来声明一个类,它表示该类可以由NestIOC容器管理

通常命名方式为XXX.service.ts

javascript 复制代码
// app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello jiusi';
  }
}

2、声明在什么地方使用

这是依赖注入的地方,一般是在类的构造函数constructor中注入,只有完成注入后才可以使用

javascript 复制代码
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/hello')
  get(): string {
    return this.appService.getHello();
  }
}

官方把appService称为tokenNestJS会根据这个token在容器中找到第1步中声明的类(这个对应关系将在第三步中进行关联注册),从而提供对应的实例,这里的实例全局唯一,只有1个!在第一次需要该实例的时候,Nestnew一个出来,而后会缓存起来,后序如果其它地方也注入了这个依赖,那Nest会从缓存中拿到之前new出来的实例供大家使用。

3、建立注入依赖与容器中类的联系

依赖注入后还需要在Module中进行关联

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

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

Nest会根据所有注入的依赖关系生成一个依赖关系图,就有点类似我们使用import引入各个模块时也会生成一个复杂的依赖关系图。这里AppController中依赖了AppService,如果AppService中还依赖其它东西也会一并放到Nest构建的依赖关系图中,Nest会从下到上按照依赖顺序构建出一整张依赖关系图保证所有的依赖关系正常运作。

四、AOP(Aspect Oriented Programming)

中文为面向切面编程。当一个请求打过来时,一般会经过 Controller(控制器)、Service(服务)、Repository(数据库访问) 的链路。当我们不使用AOP时,需要添加一些通用逻辑时(如日志记录、权限守卫、异常处理等等),就需要在每段请求逻辑中编写相关代码。

AOP就是在所有请求外面包裹一层切面,所有请求都会经过这个切面,然后我们就可以把上述的通用逻辑放在这个结构里,如下图:

在nestJS中实现AOP的方式有很多,比如(excepion filter过滤器、pipes管道、guards守卫、interceptors拦截器)。

五、NestJS请求流程图

  • Controllers -> 处理请求
  • Service -> 数据访问与核心逻辑
  • Modules -> 组合所有的逻辑代码
  • Pipes -> 管道--核验请求的数据
  • Filters -> 过滤器--处理请求时的错误
  • Guards -> 守卫--鉴权与认证
  • Interceptors -> 拦截器-给请求与响应加入额外的逻辑
  • Repositories -> 处理在数据库中数据

六、构建NestJs实际项目

1、项目创建

首先确定你已经安装了Node.js, Node.js 安装会附带npx和一个npm 包运行程序。要创建新的Nest.js 应用程序,请在终端上运行以下命令:

shell 复制代码
npm i -g @nestjs/cli  // 全局安装Nest
nest new project-name  // 创建项目

执行完创建项目, 会初始化下面这些文件, 并且询问你要是有什么方式来管理依赖包。

如果你有安装yarn,可以选择yarn,能更快一些。

注意: Nest.js 要求 Node.js(>= 10.13.0,v13 除外), 如果你的Node.js 版本不满足要求,可以通过nvm包管理工具安装符合要求的Node.js版本

2、项目结构

javascript 复制代码
src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts
app.controller.ts 单个路由的基本控制器(Controller)
app.controller.spec.ts 针对控制器的单元测试
app.module.ts 应用程序的根模块(Module)
app.service.ts 具有单一方法的基本服务(Service)
main.ts 应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例。

3、第一个接口

前面我们已经启动了服务, 那我们怎么查看呢, 首先就是找到入口文件main.ts

javascript 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

内容比较简单, 使用Nest.js的工厂函数NestFactory来创建了一个AppModule实例,启动了 HTTP 侦听器,以侦听main.ts 中所定义的端口。

监听的端口号可以自定义, 如果3000端口被其他项目使用,可以更改为其他的端口号

前边看到main.ts中也没有别的文件引入, 只有AppModule, 打开src/app.module.ts:

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

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

AppModule是应用程序的根模块,根模块提供了用来启动应用的引导机制,可以包含很多功能模块。

.mudule文件需要使用一个@Module() 装饰器的类,装饰器可以理解成一个封装好的函数,其实是一个语法糖(对装饰器不了解的,可以看走近MidwayJS:初识TS装饰器与IoC机制)。@Module() 装饰器接收四个属性:providerscontrollersimportsexports

  • providers:Nest.js注入器实例化的提供者(服务提供者),处理具体的业务逻辑,各个模块之间可以共享(注入器的概念后面依赖注入部分会讲解);
  • controllers:处理http请求,包括路由控制,向客户端返回响应,将具体业务逻辑委托给providers处理;
  • imports:导入模块的列表,如果需要使用其他模块的服务,需要通过这里导入;
  • exports:导出服务的列表,供其他模块导入使用。如果希望当前模块下的服务可以被其他模块共享,需要在这里配置导出;

app.module.ts中,看到它引入了app.controller.tsapp.service.ts,分别看一下这两个文件:

javascript 复制代码
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

使用@Controller装饰器来定义控制器, @Get是请求方法的装饰器,对getHello方法进行修饰, 表示这个方法会被GET请求调用。

javascript 复制代码
// app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService { 
  getHello(): string {
    return 'Hello World!';
  }
}

从上面,我们可以看出使用@Injectable修饰后的 AppService, 在AppModule中注册之后,在app.controller.ts中使用,我们就不需要使用new AppService()去实例化,直接引入过来就可以用。

至此,对于http://localhost:9080/接口返回的Hello World逻辑就算理清楚了, 在这基础上我们再详细的学习一下Nest.js中的路由使用。

4、路由装饰器

Nest.js中没有单独配置路由的地方,而是使用装饰器。Nest.js中定义了若干的装饰器用于处理路由。

@Controller

如每一个要成为控制器的类,都需要借助@Controller装饰器的装饰,该装饰器可以传入一个路径参数,作为访问这个控制器的主路径:

app.controller.ts文件进行修改

javascript 复制代码
// 主路径为 app
@Controller("app")
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

然后重新启一下服务。

5、HTTP方法处理装饰器

@Get@Post@Put等众多用于HTTP方法处理装饰器,经过它们装饰的方法,可以对相应的HTTP请求进行响应。同时它们可以接受一个字符串或一个字符串数组作为参数,这里的字符串可以是固定的路径,也可以是通配符。

继续修改app.controller.ts,看下面的例子:

javascript 复制代码
// 主路径为 app
@Controller("app")
export class AppController {
  constructor(private readonly appService: AppService) {}
  
  // 1. 固定路径:
  // 可以匹配到 get请求,http://localhost:9080/app/list
  @Get("list")
  getHello(): string {...}
  
  // 可以匹配到 post请求,http://localhost:9080/app/list
  @Post("list")
  create():string{...}
  
  // 2.通配符路径(?+* 三种通配符 )
  // 可以匹配到 get请求, http://localhost:9080/app/user_xxx
  @Get("user_*")
  getUser(){return "getUser"}
  
  // 3.带参数路径
  // 可以匹配到put请求,http://localhost:9080/app/list/xxxx
  @Put("list/:id")
  update(){ return "update"}
}

由于修改了文件, 需要重启才能看到路由, 每次都重启简直就是噩梦,本来打算配置一个实时监听文件变化,发现Nest.js非常贴心的配置好了, 我们只要运行命令即可:

shell 复制代码
npm run start:dev

这样再修改什么内容, 保存后都会自动重启服务了。

6、全局路由前缀

除了上面这些装饰器可以设置路由外, 我们还可以设置全局路由前缀, 比如给所以路由都加上/api前缀。此时需要修改main.ts

typescript 复制代码
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api'); // 设置全局路由前缀
  await app.listen(3000);
}
bootstrap();

此时之前的路由,都要变更为:

javascript 复制代码
http://localhost:3000/api/xxxx

到此我们认识了ControllerServiceModule、路由以及一些常用的装饰器, 那接下来就实战一下,我们以开发文章(Post)模块作为案例, 实现文章简单的CRUD,带大家熟悉一下这个过程。

七、创新新模块CURD

  • 创建服务类

nest g service posts

typescript 复制代码
// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class PostsService {}

创建app.service.ts文件,并且在app.module.ts文件下,@Module装饰器的providers中注入注入。

其实nest-cli提供的创建命令还有很多, 比如创建过滤器、拦截器和中间件等,由于这里暂时用不到,就不过多的介绍,后面章节用到了再介绍。

八、简单CURD创建

  • nest g resource user 一键搞定整个模块

    • 执行完毕后就自动生成了一个 user 文件夹,同时在app.module.ts也进行了自动导入
      来到`user.controller.ts`中我们会发现它已经帮你写好了这些请求的示例
    
typescript 复制代码
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }

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

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.userService.update(+id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.userService.remove(+id);
  }
}

我们可以看到有很多装饰器,像@Controller('user')定义的是请求路径user,而@Get,@Post等这些就代表请求方式的装饰器,比如你用 POSt 请求调用http://localhost:3000/user就会进入@Post()下面的 create()方法(这里你需要一个模拟请求的工具,比如 Apifox 或者 Postman 等),这里我使用 Apifox 进行模拟 post 请求,我们修改一下user.service.ts中的create函数

  create(createUserDto: CreateUserDto) {
    return {
        code:200,
        result:'请求成功'
    };
  }

如果我们想要获取前端 Post 请求传过来参数,可以直接用@Body装饰器即可,同样的 get 请求的话则使用@Query,这里以 post 请求为例,我们回到user.controller.ts中的 create 函数里

typescript 复制代码
@Post()
  create(@Body() createUserDto: CreateUserDto) {
    console.log(createUserDto);
    return this.userService.create(createUserDto);
  }

前端 post 携带 body 请求

如果你想直接获得 body 中的 username,你可以直接

typescript 复制代码
 create(@Body('name') name: string) {
    console.log(name);//小月
  }

看到这有小伙伴就会问了CreateUserDto干啥的,很简单,它是用来描述数据形状的,也就是说它可以定义应该接受前端传来的什么参数,参数类型等,比如在 create-user.dto.ts 中可以这样定义

typescript 复制代码
export class CreateUserDto {
  namename: string;
}

很多情况下我们需要获取前端传过来的请求头,其实在 nestjs 中获取请求头也很简单,只需要一个 Headers 装饰器即可

typescript 复制代码
  @Post()
  create(@Body() createUserDto: CreateUserDto, @Headers() headers) {
    console.log(headers);
    return this.userService.create(createUserDto);
  }

关于装饰器还有很多,由于篇幅有限这里就不再过多介绍了,大家可以到官网自行查看呦~

九、Mysql

安装电脑匹配的mysql和mysql workbench

由于我的mac系统版本问题,我安装的都是8.0.19版本,当然没有任何问题的

1、TypeORM连接数据库

前置知识

首先,简单说一下什么是ORM?

我们如果直接使用Node.js操作mysql提供的接口, 那么编写的代码就比较底层, 例如一个插入数据代码:

typescript 复制代码
// 向数据库中插入数据 
connection.query(`INSERT INTO posts (title, content) VALUES ('${title}', '${content}')`,
    (err, data) => {
    if (err) { 
    console.error(err) 
    } else {
    console.log(data) 
    }
})

考虑到数据库表是一个二维表,包含多行多列,例如一个posts的表:

typescript 复制代码
mysql> select * from posts;
+----+--------+------------+
| id | title       | content      |
+----+-------------+--------------+
|  1 | Nest.js入门 | 文章内容描述 |
+----+--------+------------+

每一行可以用一个JavaScript对象来表示, 比如第一行:

typescript 复制代码
{
    id: 1,
    title:"Nest.js入门",
    content:"文章内容描述"
}

这就是传说中的ORM技术(Object-Relational Mapping),把关系数据库的表结构映射到对象上。

所以就出现了SequelizetypeORMPrisma这些ORM框架来做这个转换, (ps:Prisma呼声很高,喜欢探索的可以尝试一下)我们这里选择typeORM来操作数据库。 这样我们读写都是JavaScript对象,比如上面的插入语句就可以这样实现:

typescript 复制代码
    return await this.userRepository.save(createUserDto);

接下来就是真正意义上的使用typeORM操作数据库, 首先我们要安装以下依赖包:

typescript 复制代码
npm install @nestjs/typeorm typeorm mysql2 -S

连接数据库的方法:

在app.mudule.ts文件中使用TypeOrmModule.forRoot:

typescript 复制代码
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    UserModule,
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'lt851328',
      database: 'nestjs',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})

十、CRUD代码编写

接下来我们根据前端传来的参数对数据库进行一个简单的 crud 操作,注意这里只是演示,少了一些逻辑判断。 首先在user.module.ts中导入数据库相关东西

typescript 复制代码
import { Module } from "@nestjs/common";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
import { UserEntity } from "./entities/user.entity";
import { TypeOrmModule } from "@nestjs/typeorm";
@Module({
  imports: [TypeOrmModule.forFeature([UserEntity])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

此时在create-user.dto.ts 定义一下接收前端传来的参数

typescript 复制代码
export class CreateUserDto {
  username: string;
  password: string;
}

然后在user.service.ts进行数据库数据的操作,代码如下:

typescript 复制代码
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserEntity } from './entities/user.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
  ) {}
  create(createUserDto: CreateUserDto) {
    console.log(createUserDto);
    return this.userRepository.save(createUserDto);
  }

  async findAll() {
    return await this.userRepository.find();
  }

  findOne(id: number) {
    return `This action returns a #${id} user`;
  }
  
  async update(id: number, updateUserDto: UpdateUserDto) {
    const db = this.userRepository.createQueryBuilder();
    return await db.update(updateUserDto).where({ id }).execute();
  }

  async remove(id: number) {
    const db = this.userRepository.createQueryBuilder();
    return await db.delete().where({ id }).execute();
  }
}

十一、统一接口规范

通过上面的一通操作,细心的小伙汁肯定发现了上面接口返回的数据是个什么玩意.要状态没状态,要描述没描述的,这样拿给前端前端不得揍死你。所以为了把最好的留给前端,我们还需要对接口返回进行一个统一数据封装。

一般接口返回的数据大致格式可能如下

typescript 复制代码
{
    data:业务参数,
    code:状态码,
    describe:状态描述
    ...
}

1、filter过滤器

首先我们使用命令新建一个过滤器,用来抛出我们需要返回给前端的错误码以告知前端传来的是错误请求

bash 复制代码
nest g filter common/filter/http-exception

然后修改一下http-exception.filter.ts

typescript 复制代码
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response.status(status).json({
      code: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}
// This is a custom exception filter that will catch all HttpExceptions and return a JSON response with the status code, timestamp, and path of the request.
// The catch method takes two arguments: exception and host. exception is the HttpException that was thrown, and host is an ArgumentsHost object that provides access to the request and response objects.
// Inside the catch method, we switch to the HTTP context using the switchToHttp method and get the response and request objects using the getResponse and getRequest methods.

在 main.ts 中注册

typescript 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filter/http-exception/http-exception.filter';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

这时候我们随便找个接口抛出一个错误码试一下,就你了findAll方法

typescript 复制代码
  async findAll() {
    throw new HttpException('错误的请求', 401);
    return await this.userRepository.find();
  }

然后我们再创建一个拦截器对请求成功的数据进行格式化

2、interceptor拦截器

然后我们再创建一个拦截器对请求成功的数据进行格式化

同样的使用

bash 复制代码
nest g interceptor common/interceptor/transform

创建一个拦截器,直接把官网示例抄过来transform.interceptor.ts

typescript 复制代码
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  data: T;
}

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>>
{
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next
      .handle()
      .pipe(map((data) => ({ code: 200, data, describe: '请求成功' })));
  }
}

以下是对这段代码的详细解释:

一、导入模块

typescript 复制代码
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
  • Injectable:来自@nestjs/common,用于将一个类标记为可注入的服务。在这里,它用于标记TransformInterceptor类可以被 NestJS 的依赖注入系统管理。
  • NestInterceptorExecutionContextCallHandler也来自@nestjs/commonNestInterceptor是一个接口,用于定义拦截器。ExecutionContext提供了关于当前正在处理的请求的上下文信息,包括请求和响应对象等。CallHandler表示对路由处理函数的调用,可以用来控制请求的执行流程。
  • Observable来自rxjs,用于处理异步操作。在 NestJS 中,许多异步操作都是通过 Observable 来实现的。
  • map来自rxjs/operators,用于对 Observable 发出的值进行转换。

二、定义接口

typescript 复制代码
export interface Response<T> {
  data: T;
}

这里定义了一个泛型接口Response,它表示一个响应对象,包含一个泛型类型的数据字段data

三、定义拦截器类

typescript 复制代码
@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>>
{
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next
     .handle()
     .pipe(map((data) => ({ code: 200, data, describe: '请求成功' })));
  }
}
  • @Injectable()装饰器将TransformInterceptor类标记为可注入的服务,以便在 NestJS 的依赖注入系统中使用。
  • implements NestInterceptor<T, Response<T>>表示这个类实现了NestInterceptor接口,泛型参数T表示请求处理函数返回的数据类型,而Response<T>是拦截器处理后返回的响应类型。
  • intercept方法是拦截器的核心方法,它接收两个参数:ExecutionContext类型的contextCallHandler类型的next
    • context提供了关于当前请求的上下文信息,可以从中获取请求和响应对象等。
    • next是一个CallHandler,调用next.handle()会触发后续的请求处理流程,即执行路由处理函数。
  • return next.handle().pipe(map((data) => ({ code: 200, data, describe: '请求成功' })));这行代码首先调用next.handle()来触发后续的请求处理流程,然后使用pipemap操作符对处理结果进行转换。map操作符接收一个函数,这个函数将原始的处理结果data转换为一个包含状态码code、数据data和描述describe的对象,这里将状态码设置为 200,并添加了描述"请求成功"。最终返回一个新的Observable,其发出的值是经过转换后的响应对象。

这个拦截器的作用是在请求处理完成后,对响应数据进行统一的格式转换,添加状态码和描述信息,以便在整个应用中提供一致的响应格式。

最后

和过滤器一样在 main.ts 中注册

typescript 复制代码
app.useGlobalInterceptors(new TransformInterceptor());

然后试一下查询接口,到这里我们就完成了返回结果的一个简单封装

相关推荐
初遇你时动了情23 分钟前
uniapp 城市选择插件
开发语言·javascript·uni-app
zongzi_4941 小时前
二次封装的天气时间日历选择组件
开发语言·javascript·ecmascript
麻辣_水煮鱼2 小时前
vue数据变化但页面不变
前端·javascript·vue.js
一条晒干的咸魚2 小时前
【Web前端】实现基于 Promise 的 API:alarm API
开发语言·前端·javascript·api·promise
WilliamLuo2 小时前
MP4结构初识-第一篇
前端·javascript·音视频开发
过期的H2O23 小时前
【H2O2|全栈】JS进阶知识(七)ES6(3)
开发语言·javascript·es6
前端Hardy3 小时前
HTML&CSS:数据卡片可以这样设计
前端·javascript·css·3d·html
松树戈3 小时前
JS推荐实践
开发语言·javascript·ecmascript
vener_3 小时前
LuckySheet协同编辑后端示例(Django+Channel,Websocket通信)
javascript·后端·python·websocket·django·luckysheet
老码沉思录3 小时前
React Native 全栈开发实战班 - 性能与调试之打包与发布
javascript·react native·react.js