Nest.js 系列——搭建 web 接口开发框架(过滤器、拦截器、swagger 等)
前言
本专栏的上一篇文章《Nest.js 系列——从零搭建多配置开发环境》配置好了一个多环境开发的框架,在此基础之上,再配置一些关于 web 接口开发相关的,比如统一返回体、统一捕获错误、认证鉴权、swagger 文档等,希望通过这篇文章能有一个直接上手写业务的模版,方便复刻与修改。
配置链接 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 接口需要注意的地方,还有其他内容下次再整理,希望对你有帮助!!!