Skip to content

Nest.js 系列——搭建 web 接口开发框架(过滤器、拦截器、swagger 等)

前言

本专栏的上一篇文章《Nest.js 系列——从零搭建多配置开发环境》配置好了一个多环境开发的框架,在此基础之上,再配置一些关于 web 接口开发相关的,比如统一返回体、统一捕获错误、认证鉴权、swagger 文档等,希望通过这篇文章能有一个直接上手写业务的模版,方便复刻与修改。

配置链接 mysql 数据库

其实这部分上篇文章也有提到就是通过 typeorm 来连接数据库,并使用 typeorm 来查数据库数据。这里再单独记录下多种配置方法

首先需要安装需要的依赖

shell
npm install @nestjs/typeorm typeorm mysql2 -S

nest 提供两种配置方式

1. 直接在 appModule 中注册

ts
TypeOrmModule.forRootAsync({
    imports: [ConfigModule], inject: [ConfigService],
    useFactory: async (configService: ConfigService) => ({
    type: 'mysql', // 数据库类型
    entities: [], // 数据表实体
    host: configService.get('DB_HOST', 'localhost'), // 主机,默认为localhost
    port: configService.get<number>('DB_PORT', 3306), // 端口号
    username: configService.get('DB_USER', 'root'), // 用户名
    password: configService.get('DB_PASSWORD', 'root'), // 密码
    database: configService.get('DB_DATABASE', 'blog'), //数据库名
    timezone: '+08:00', //服务器上配置的时区
    synchronize: true, //根据实体自动创建数据库表, 生产环境建议关闭 }),
})

具体可以参考上篇文章

2. 在根目录下创建一个 ormconfig.json 文件(与 src 同级), 而不是将配置对象传递给 forRoot()的方式。

先创建一个 ormconfig.json 文件

json
{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "root",
  "password": "root",
  "database": "blog",
  "entities": ["dist/**/*.entity{.ts,.js}"],
  "synchronize": true // 自动载入的模型将同步
}

然后在 appModule 中使用

ts
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
@Module({
  imports: [TypeOrmModule.forRoot()]
})
export class AppModule {}

其实也可以直接注入配置信息的

ts
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'admin',
      password: 'admin',
      database: 'test',
      entities: ['dist/**/*.entity{.ts,.js}'],
      synchronize: false,
      autoLoadEntities: true
    })
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}

统一返回结果拦截器

在数据请求接口中,作为前端来获取数据,肯定是希望能拿到相同数据格式的数据,但是如果在写接口的时候,在每个接口中对数据进行处理和返回,那很显然是不可取的,太繁琐了。这时候全局拦截器就派上用场了,给所有的返回结果做一层处理

先创建一个拦截器文件,文件的位置自行决定,只要能引用到并完成注册就行

ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { map, Observable } from 'rxjs'

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

使用上需要在 main.ts 上注册全局过滤器

javascript
import { TransformInterceptor } from './transform.interceptor'

// 使用全局拦截器格式化返回结果
app.useGlobalInterceptors(new TransformInterceptor())

全局错误过滤器

在接口请求的过程中,难免会出现这样或那样的错误,如果返回的错误信息不具体,那么查找问题起来就是非常麻烦的了,全局错误处理可以使用全局过滤器,对错误进行处理

先新建一个自定义过滤器

ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp() // 获取请求上下文
    const response = ctx.getResponse() // 获取请求上下文中的 response对象
    const status = exception.getStatus() // 获取异常状态码

    // 设置错误信息
    const message = exception.message
      ? exception.message
      : `${status >= 500 ? 'Service Error' : 'Client Error'}`
    const errorResponse = {
      data: {},
      message: message,
      code: -1
    }

    // 设置返回的状态码, 请求头,发送错误信息
    response.status(status)
    response.header('Content-Type', 'application/json; charset=utf-8')
    response.send(errorResponse)
  }
}

写好自定义过滤器后需要在 main.ts 中全局注册使用

ts
import { HttpExceptionFilter } from './filter/http-exception';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  ...
  // 全局注册拦截器
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(9080);
}
bootstrap();

当请求错误就可以统一返回,返回请求错误只需要抛出错误就可以了

ts
throw new HttpException('已存在'401)

api 文档

当写好接口之后,要把接口信息同步给前端或者其他同事。这时候就会用到 api 文档,当然 api 文档你可以用任何方式记录,甚至是 word 文档。但是这有点太不程序猿了。所以一般能自动的绝不手动,把项目中接入 swagger 来自动生成 api 文档。

先安装 swagger 相关的依赖包

shell
npm install @nestjs/swagger swagger-ui-express -S

然后需要在 main.ts 中进行相关的设置配置

ts
...
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  ...
  // 设置swagger文档
  const config = new DocumentBuilder()
    .setTitle('管理后台')
    .setDescription('管理后台接口文档')
    .setVersion('1.0')
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('docs', app, document);

  await app.listen(9080);
}
bootstrap();

接口标签

但是这样虽然能显示出接口文档,但是并没分类,看起来不够清晰,所以需要通过加接口标签来分类。可以根据 Colltroller 来进行分类,使用@ApiTags 装饰器就行了

ts
...
import { ApiTags } from '@nestjs/swagger';
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';

@ApiTags("文章")
@Controller('post')
export class PostsController {...}

接口描述

如上虽然对接口做了分类,但是每一个接口是做什么的并没有一个简单的说明,为了看文档更加方便,优化一下加一些文字描述说明,给每一个接口加上说明之后,能够直观的理解当前接口的作用什么,可以通过@ApiOperation 装饰器来加上描述

ts
//  posts.controller.ts
...
import { ApiTags,ApiOperation } from '@nestjs/swagger';
export class PostsController {

  @ApiOperation({ summary: '创建文章' })
  @Post()
  async create(@Body() post) {....}

  @ApiOperation({ summary: '获取文章列表' })
  @Get()
  async findAll(@Query() query): Promise<PostsRo> {...}
  ....
}

数据验证校验

对于开发来说,前端会传递任何参数,如果传递的参数不是后端接口想要的,而且也没有做容错处理,那么就有可能造成不必要的麻烦,导致接口报错,服务不可用什么的。基于代码的严谨,后端在拿到前端传递的数据之后应该做一次校验,如果校验不通过就不再继续直接返回错误,这里就会用到之前基础中说的管道

安装验证需要的依赖包

shell
npm install class-validator class-transformer -S

使用数据验证管道

ts
import { IsNotEmpty, IsNumber, IsString } from 'class-validator'

export class CreatePostDto {
  @ApiProperty({ description: '文章标题' })
  @IsNotEmpty({ message: '文章标题必填' })
  readonly title: string

  @IsNotEmpty({ message: '缺少作者信息' })
  @ApiProperty({ description: '作者' })
  readonly author: string

  @ApiPropertyOptional({ description: '内容' })
  readonly content: string

  @ApiPropertyOptional({ description: '文章封面' })
  readonly cover_url: string

  @IsNumber()
  @ApiProperty({ description: '文章类型' })
  readonly type: number
}

全局注册生效

然后在全局 main.ts 中开启验证

ts
app.useGlobalPipes(new ValidationPipe())

这样就会对前端传递过来的数据做一次校验,提前报出错误。

小结

简单整理下开发 api 接口需要注意的地方,还有其他内容下次再整理,希望对你有帮助!!!

如有转载或 CV 的请标注本站原文地址