从零开始的 Nest.js
Nest.js 久有耳闻了,但是一直没有时间去真正学习他,一直鸽子到了现在。我想借着学习 nest 的先进思想,来重构我的博客后端。
Nest.js 是一个基于 Express.js 的渐进式 Web 框架,一提到 express 很多人就觉得性能太弱,不太适合使用,但是它的生态好,也正是这一点 Nest.js 选择了 Express.js 作为底层。
Nest.js 现已支持更快的 fastify 作为底层框架
准备
首先安装 nest-cli,前往官方阅读相关内容,不多赘述。
使用 nest new server
建立一个 nest 项目。
打开项目目录,我们会看到 nest-cli 帮我们建立了一个 app.module
作为根模块。app.service
作为逻辑处理模块,app.controller
作为控制器模块。
路由
nest 中的路由是位于一个被Controller
装饰的类中,每个路由是该类中的一个方法,该方法被Get``Post
等装饰器装饰,而返回的值则是响应对象。
Swagger
正是因为 nest 集成了 swagger 自动生成文档,我对他产生了非常好的影响。不用手动写文档,根据模型的字段和类型就能生成每个 api 的文档。简直不要太爽。
首先我们安装 swagger 对应的库。
yarn add @nestjs/swagger swagger-ui-express
在 app.module
中加入
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
即完成初始化。
随后在每个路由上,你也可以添加一些装饰器在路由上,swagger 会生成描述等。
在控制器对象上加上 ApiTags
装饰器,即可对不同控制器加以分组。
@Controller('master')
@ApiTags('Master Routes')
export class MasterController {
// ...
}
同样如果想要某个路由在 swagger 中显示参数,则可以对参数设置一个类型,或是一个 Dto 模型。比如:
// user.controller.ts
@Post('/sign_up')
async register(@Body() userDto: UserDto) {
return await this.masterService.createMaster()
}
// user.dto.ts
import { ApiProperty } from '@nestjs/swagger'
export class UserDto {
@ApiProperty()
readonly username: string
@ApiProperty()
readonly password: string
}
只要属性被 ApiProperty
装饰,该属性就会被 swagger 读取。
请求过滤
nest 同样提供了强大了请求过滤,你可以使用之前为 swagger 准备的 Dto 模型,在此基础上加以扩展,即可对请求体的模型进行验证。验证通过 nest 的管道(Pipe)。
首先安装 class-validator
,之后 main.ts
中引入全局管道 ValidationPipe
,ValidationPipe
是 nest 提供的一个类似于Joi
之类的 Schema 验证器,他通过调用 class-validator
来识别该属性是否正确或者需要,已阻止不必要的 nosql 注入。
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 白名单模式,过滤 dto 中未定义的属性
transform: true, // 自动转换,比如 req.age = "12" => req.age = 12
}),
)
// ...
}
在之前的 Dto 中,稍加扩展。
// user.dto.ts
import { ApiProperty } from '@nestjs/swagger'
import {
IsString,
IsNotEmpty,
} from 'class-validator'
export class UserDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
readonly username: string
@IsString()
@ApiProperty()
@IsNotEmpty()
readonly password: string
}
再次请求接口。
对 /user/login
接口发起一个 nosql 注入请求
{
"username": "admin",
"password": {"$gt": 1}
}
得到了一个 400 响应。
通过这种方式,可以少些很多判断类型的代码。实在是高。