REST API 数据传输

通过 URL

最基本的通过 url 传输形式是这样的:

js
1http://localhost/user/:id

通过以下命令,我们先创建一个 user 的 controller

bash
1nest generate controller user

通过@Get(':id')来声明路由 以及通过@Param(参数名)装饰器取出参数注入到 controller:

js
1@Controller('user')
2export class UserController {
3  @Get(':id')
4  urlParam(@Param('id') id: string) {
5    return `received: id=${id}`
6  }
7}

现在@Controller('user')@Get(':id')会拼接成为/user/:id这样的 URL。

执行pnpm start:dev命令,我们启动 nest server,默认的端口号为 3000,这些都是在main.ts中的bootstrap中写好的:

js
1async function bootstrap() {
2  const app = await NestFactory.create(AppModule)
3  await app.listen(3000)
4}
5bootstrap()

现在我们在浏览器访问http://localhost:3000/user/1,这个GET请求会经过@Controller('user')@Get(':id')装饰器下的urlParam方法,将结果返回给我们:

js
1received: id = 1

通过 query

通过 query 传输的形式是这样的:

js
1http://localhost/user?id=xxx&age=30

如果用 query 传输,可以使用 query-string或者 qs将对象解析成xx=xx&yy=yy这样的字符串编码,同时这些三方库会对非英文字符做编码处理。

如果自己写的话,则需要手动调用 API 对一些非英文字符做编码。

对整个 URL 做编码使用encodeURI,单独对部分做编码则使用encodeURIComponent

js
1const query = '?name=' + encodeURIComponent('加密')
2// '?name=%E5%8A%A0%E5%AF%86'

跟 url param 类似,我们继续用装饰器定义一个新的方法:

ts
1  @Get('find')
2  urlQuery(@Query('id') id: string, @Query('name') name: string) {
3    return `received from Query: id=${id} name=${name}`;
4  }

不同之处在于没有使用:声明,以及提取参数用的装饰器为@Query('参数名')

继续在浏览器访问:

js
1http://localhost:3000/user/find?id=123&name=qiuyanxi

可以得到结果:

js
1received from Query: id=123 name=qiuyanxi

注意现在的代码:

ts
1@Controller('user')
2export class UserController {
3  // query在前
4  @Get('find')
5  urlQuery(@Query('id') id: string, @Query('name') name: string) {
6    return `received from Query: id=${id} name=${name}`
7  }
8  // param在后
9  @Get(':id')
10  urlParam(@Param('id') id: string) {
11    return `received: id=${id}`
12  }
13}

这是因为Controller内的方法会按照顺序匹配,当访问user/find时,也可能会被user/:id路由匹配到。

通过 form-urlencoded

直接使用 form 表单提交时是采用form-urlencoded进行传输。

content-typeapplication/x-www-form-urlencoded

它的意思是:“这是已编码为 URL 参数的表单数据。”

这是一种使用 Post 请求并将 url query放在 body 传输的方法。所以非英文的参数都需要经过编码(encode)。(方法同 query)

http
1POST /test HTTP/1.1
2Host: foo.example
3Content-Type: application/x-www-form-urlencoded
4Content-Length: 27
5
6name=%E5%8A%A0%E5%AF%86&field2=value2

在 Nest 中接受 body 内的数据,需要用到 @Body 装饰器,Nest 会解析请求体,注入到 dto(data transfer object)中。

先定义一个 dto 对象

ts
1// src/dto/person.dto.ts
2
3export class PersonDto {
4  name: string
5  age: number
6}

再使用@Post装饰器定义路由以及使用@Body装饰器取出 Body 内的参数。

ts
1import { PersonDto } from 'src/dto/person.dto'
2
3@Controller('user')
4export class UserController {
5  @Post('get')
6  body(@Body() getPersonDto: PersonDto) {
7    return `received: ${JSON.stringify(getPersonDto)}`
8  }
9}

使用 postman 请求测试一下

image-20230929152010323

通过 JSON

在 json 数据格式被广泛用于 HTTP 传输后,大多数的 REST API 都使用 json 传输数据。

content-typeapplication/json

都是从 Body 中取值,Nest 内部做了封装,能够对应不同的 content-type 从 body 中拿到数据。所以代码几乎没有变化。

ts
1  @Post('get')
2  getUserByJSON(@Body() getPersonDto: PersonDto) {
3    return `received: ${JSON.stringify(getPersonDto)}`;
4  }

唯一变化的就是前端请求时,需要修改content-type

image-20230929153749565

通过 formdata

如果你想要发送文件,你需要使用 formdata 的方式传输。

content-typemultipart/form-data

因为数据将被分成多个部分,每个文件单独占用一个部分,表单正文中包含的文本数据(如果文本也输入到表单中)占用一个部分。

Nest 解析 form data 使用 FilesInterceptor 的拦截器,用 @UseInterceptors 装饰器启用,然后通过 @UploadedFiles 来取。

非文件的内容,同样是通过 @Body 来取。

接受文件的 Nest 代码如下:

ts
1  @Post('form-data')
2  @UseInterceptors(
3    AnyFilesInterceptor({
4      dest: 'uploads/',
5    }),
6  )
7  body2(
8    @Body() getPersonDto: PersonDto,
9    @UploadedFiles() files: Array<Express.Multer.File>,
10  ) {
11    console.log('——————🚀🚀🚀🚀🚀 —— files:', files);
12    return `received: ${JSON.stringify(getPersonDto)}`;
13  }

上面的代码还需要额外安装Express.Multer的类型声明

bash
1pnpm install -D @types/multer

接着用 postman 测试一下:

image-20230929160600564

现在文件已经成功传输到dest: 'uploads/'中:

image-20230929161139952

总结

Nest 对每种不同的 REST API 都做了封装,并且有不同的 Typescript 装饰器方便我们获取数据。

其中使用 URL 传输的为:

  • url param,即http://localhost/user/:id,使用@Param 装饰器取数据

  • url query ,即http://localhost/user?id=xxx,使用@Query 装饰器取数据

使用 body 传输的为:

  • form-urlencoded,将 url query 字符串 用 post 方式传输,使用@Body()装饰器取数据

  • json,json 数据传输,使用@Body()装饰器取数据

  • form-data ,适用于文件与文本数据传输,使用@UploadedFiles 装饰器取数据

此外,还有一些装饰器:

  • @Get 装饰器声明 get 请求。
  • @Post 装饰器声明 post 请求。
  • @UseInterceptors 装饰器启用拦截器。
  • @AnyFilesInterceptor 装饰器对文件进行拦截并且解析。