前言
近年来,Nest 开源项目用于服务端的开发很火爆。Nest 支持 TS、GraphQL等,可以快速、高效地开发一个 server 端项目。
在前面的内容中,我们探索了 Nest 的基本原理、中间件、守卫、日志等,然而,光说不练假把式,只知基本原理却不知如何开发最基本的 CRUD 接口,岂不可笑?因此,本文介绍了如何使用 Nest 去开发一个能提供基础 CRUD 接口的 server 端项目。
新建项目
首先,我们快速新建一个 nest 项目:
nest new nest-decorators-demo-240505 -p pnpm
我们新建项目后,访问 http://localhost:3000/ 就能成功看到响应文字了。
显而易见的是,我们使用最多的是 @Get() 和 @Post() 装饰器,分别是用于定义响应 Get 请求和 Post 请求的方法的装饰器。类似地,还有 @Put(), @Delete(), @Patch(), @Options(), 和 @Head() 等装饰器,可以响应定义其他请求类型的函数。而 @All() 装饰器则是定义用于处理特定路由路径的所有请求的方法。
此外,类比 Koa2 的话,如果我们想响应 http://localhost:3000/cats ,或者 http://localhost:3000/cats/tom 等请求,需要怎么操作呢?
设置路由前缀
我们知道,一个 Nest 应用使用 Controller 去处理客户端的请求,并进行响应。
这里,我们新建一个 cats.controller.ts 文件,通过指定@Controller('cats'),也就是指定了当前文件用于处理路由前缀为 /cats 的请求。接下来,如果我们访问链接 http://localhost:3000/cats 时,nest 服务端会响应 All cats 。
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'All cats';
}
}
类似地,我们指定一个 findTom 方法用于处理 http://localhost:3000/cats/tom 的请求:
@Get('tom')
findTom() {
return 'Cat tom';
}
路由通配符
Nest 也支持路由通配符的写法,比如'ab*cd'路径可以匹配到abcd, ab_cd, abecd等请求。
当字符?, +, *, 和 () 作为请求路径时,会被当作正则表达式去解析。比如ab?cd可以匹配acd和abcd。
而字符串短线(-)和点(.)会被视为字符串。
需要注意的是,路径中间的路由通配符仅有 express 是支持的。
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
@Param(), @Bind()
在项目开发中,在请求链接上传递参数、获取参数是很普遍的做法。那么,我们是怎么操作的呢?
在 nest 应用中,设置路由参数可以通过 @Get(':id') 传参:id的形式:
@Get(':id')
@Bind(Param())
findOne(params) {
console.log(params.id);
return `Cat ${params.id}`;
}
这样,当我们访问链接 /cats/aaa、/cats/bbb 等就会被当前函数匹配到,并进一步处理访问请求。
如果我们想在 findOne 函数中获取请求参数,这里可以使用装饰器@Bind(Param())。根据文档可知,@Bind() 装饰器的作用是对接下来的函数绑定了参数装饰器。Param() 装饰器的作用是获取 req 对象的 params 属性。
/**
* Decorator that binds *parameter decorators* to the method that follows.
*/
export declare function Bind(...decorators: any[]): MethodDecorator;
/**
* Route handler parameter decorator. Extracts the `params`
* property from the `req` object and populates the decorated
* parameter with the value of `params`. May also apply pipes to the bound
* parameter.
*
* For example, extracting all params:
* ```typescript
* findOne(@Param() params: string[])
* ```
*
* For example, extracting a single param:
* ```typescript
* findOne(@Param('id') id: string)
* ```
* @param property name of single property to extract from the `req` object
* @param pipes one or more pipes - either instances or classes - to apply to
* the bound parameter.
*
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
* @see [Working with pipes](https://docs.nestjs.com/custom-decorators#working-with-pipes)
*
* @publicApi
*/
export declare function Param(): ParameterDecorator;
此外,在 findOne 函数中获取请求参数还可以通过下面的写法:
@Get(':id')
findOne(@Param() params: string[]) {}
@HttpCode(), @Header()
借助 @HttpCode() ,我们可以很方便地修改响应状态码。比如修改为 204 No Content;借助 @Header() ,我们可以修改响应头部。比如设置 Cache-Control 为 no-cache。
需要注意的是,GET 请求的默认 HTTP 状态码是 200 ,而 POST 请求默认是 201 。
@Get('test')
@HttpCode(204)
@Header('Cache-Control', 'no-cache')
test() {
return {
data: 'Cat test',
};
}
响应情况为:
@Redirect(), @Query()
@Redirect() 装饰器带有两个参数:url 和 statusCode,这两个参数均为可选的。当不传参时,statusCode 的默认值是 302 (Found)。
同时,我们可以通过@Query('version') version获取对应的路由传参,结合路由重定向的逻辑进行判断,比如版本是5时重定向到 /v5/ 路径。举个例子:
@Get()
@Redirect('https://nestjs.com', 301)
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}
浏览器分别访问:
@Body()
在 Nest探索(五)Nest 实现文件上传功能、学习e2e测试方法 一文中,我们使用过@Body()了,并借助class-validator包进行传参校验。而 class-transformer 包是将字面量对象转化为相应的类生成的对象,然后就可以使用 class-validator 进行验证了。
首先,安装项目依赖:
pnpm i class-validator class-transformer
简而言之,传参是通过 dto 定义的,比如这里是使用 @Body() 装饰器声明的 CreateCatDto 类。
@Post('create')
async create(@Body() createCatDto: CreateCatDto) {
console.log('createCatDto', createCatDto);
return 'Create a new cat';
}
// create-cat.dto.ts
import { IsNotEmpty, Length } from 'class-validator';
export class CreateCatDto {
@IsNotEmpty({ message: '名字不为空' })
@Length(1, 20, { message: '名字的长度不能小于1且不能大于20' })
name: string;
age: number;
color: string;
}
在 main.ts 中,通过 ValidationPipe 设置全局管道:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
后记
在本文中,我们介绍了路由相关的装饰器,并了解了如何去获取路由传参和路由参数等,这有助于我们快速地进行 CRUD 接口的开发。
此外,我们结合之前探索的 Nest 的基本原理、中间件、守卫、日志等知识,就可以高效地完成一个基础的后端项目的开发和应用了。