Nest.js系列——搭建web接口开发框架模版(一)

782 阅读6分钟

前言

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

项目代码仓库地址https://github.com/wangyuan0108/nest_web

配置链接mysql数据库

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

首先需要安装需要的依赖

npm install @nestjs/typeorm typeorm mysql2 -S

nest提供两种配置方式

1. 直接在appModule中注册

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文件

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

然后在appModule中使用

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

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

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

统一返回结果拦截器

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

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

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上注册全局过滤器

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

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

全局错误过滤器

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

先新建一个自定义过滤器

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中全局注册使用

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();

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

throw new HttpException('已存在'401)

api文档

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

先安装swagger相关的依赖包

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

然后需要在main.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装饰器就行了

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

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

接口描述

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

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

数据验证校验

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

安装验证需要的依赖包

npm install class-validator class-transformer -S

使用数据验证管道

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中开启验证

app.useGlobalPipes(new ValidationPipe());

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

小结

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