前言
之前匆匆写了一篇关于Nest官文的翻译,然而写的时候比较着急,所以很多地方的翻译比较马虎,甚至直接丢进翻译器在丢出来....(有的时候我会被丢进翻译器之后被丢出来的译文吓到,原因是...译文比自己翻译的好/(ㄒoㄒ)/~~)但还是有很多部分是翻译器解决不了的,语句通顺固然优雅锦上添花,但一点小的错误却是致命的。
现在手头比较悠闲,也打算重新修改一份比较优雅的中文文档。有人会问花这么多时间写这个东西,是不是真的有用。百度上面也有一些关于Nest的文档,完全也不会有人来看你写的翻译。我的感受是,可能这就是我的学习方式吧。其实平时阅读文档,大多数情况下都是脑子说会了,手说不会。一边翻译英文文档,一遍理解框架的含义,还有助于提高阅读英文文档的能力。手敲过一遍和眼睛看过一遍真的不太一样,而且这种方式会增加使用时的自信。之后打算将每一章节分开书写,最后再通过链接汇总到一篇Nest妲己大记中去。就算没有人看,自己想要翻阅文档的时候,也可以拿出来看看,还能改改。这是一种乐趣,就好像养成游戏一样。
正文 Controllers
controllers(控制器)负责处理请求并返回响应数据给客户端
- controller的目的是为应用 接收特定的路由请求。 路由策略控制哪一个controller接受哪些请求。通常每个controller拥有多个路由,并且不同的路由可以执行不的动作。
- 创建一个基础的controller可以使用 class(类) 和 decorators(装饰器)。装饰器将类和需要的 metadata(元数据)结合,Nest以此来创建一个路由映射(将请求和对应的controller联系起来)。
Routing 路由
如下例,定义一个基础的controller需要使用装饰器
@Controller()。
指定一个可配置的路由路径前缀
cats
将一系列相关的路由组合起来,最大程度减少重复代码。
🌰 我们将一组路由分组,使用decorator
@Controller()
在里面指定路由的前缀
customers
用来管理所有
/customers
请求下的交互,如此便不必在整个文件中重复得去书写相同的路径。
// cats.controller.ts
imoprt { Controller, Get } from '@nestjs/common'
@controller('cats')
export class CatsController {
@Get()
findAll(): string {
return '这个动作会返回所有的cats'
}
}
Hints
可以使用CLI创建controllernest g controller cats
在 findAll() 方法之前添加HTTP请求方法的装饰器 @Get() 用来告诉Nest为HTTP请求特定的端点创建一个处理程序。这个端点对应了 HTTP请求方法 (这里是 GET)和 路由的路径。路径由 控制器中声明的前缀 和 在请求装饰器中指定的路径 组合组成。 我们为每个路由声明了 cats 的前缀,若没有在装饰器中添加任何信息,Nest会将 GET /cats 请求映射到这个处理函数中。
🌰 路由前缀为 customers 加上装饰器 @Get('profile') 会将请求映射到路由 GET /customers/profile
在上例中,当一个 GET 请求到这个端点,Nest将这个请求路由到用户定义的 findAll() 函数中。注意这个方法的名字是随意的,我们很显然必须为路由绑定一个函数,但是这个函数的名字Nest并不在意~
这个方法只会返回一个200的状态码和对应的响应数据(上例中只是一个字符串)。解释这个问题,需要先介绍Nest,它使用两种不同的选项来处理响应。
Standard <标准的>(推荐) | 使用内置的方法,当一个请求的处理函数返回了一个js对象或数组,将自动序列化为JSON格式。当返回的是一个基础js类型,Nest会直接将值返回,不会进行序列化。这响应的处理变得简单:只返回值,Nest会关心剩下的处理 |
Library-specific <指定库> | 我们可以使用指定库(例如Express)的响应对象,通过在处理函数签名中使用装饰器@Res()(例如findAll(@Res() response))注入。如此你就拥有能力(和责任)来使用原生响应处理函数所暴露的对象。例如:使用Express,你可以用response.status(200).send()构建响应数据 |
Warning
不可以同时使用以上两种方法。Nest会检查你是否使用@Res()或者@Next(),这表明了你选择了Library-specific选项。如果同时使用了两种方法,在当前路由中Standard会自动禁用,也不会起效果哦~
Request Object 请求对象
处理函数通常需要接受来自客户端的请求细节。Nest提供了底层请求对象的接口(默认是Express)。我们可以通过在函数签名中添加 @Req() 装饰器来获取请求对象
// cats.controller.ts
import { Controller, Get, Req } from '@nestjs/common'
import { Request } from 'express'
@Controller('cats')
export class CatsController {
@Get('cats')
findAll(@Req() request: Request): string {
return '这个动作返回所有的cats'
}
}
该请求对象代表了HTTP请求,并且拥有查询字符串,HTTP请求头,和请求体等属性。大多数情况下不需要手动获取这些属性,而是直接使用装饰器比如 @Body() 或者 @Query() 。下面是提供的装饰器以及他们所代表的相应的对象
@Request() | req |
@Response(), @Res()* | res |
@Next() | next |
@Session() | req.session |
@Param(key?: string) | req.params / req.params[key] |
@Body(key?: string) | req.body / req.body[key] |
@Query(key?: string) | req.query / req.query[key] |
@Headers(name?: string) | req.headers / req.headers[name] |
@Ip() | req.ip |
* 为了兼容底层HTTP类型,Nest同时提供了 @Res() 和 @Response() 装饰器, @Res() 只是 @Response() 的别名。两者都暴露了底层响应对象的接口,使用的时候需要引入这些底层库的类型。当你同时注入二者在同一个处理函数中,表示你为该函数选择了Library-specific模式,你就有义务处理响应的对象。你必须在响应对象上调用一些方法来发出响应(例如:res.json())或者@res.send(),否则HTTP服务将会处于挂起状态。
Hints
学习创建自定义装饰器
Resource 资源
前面定义了一个端点来获取cats的资源(GET 路由),我们也需要提供一个端点来创建新的资源。
// cats.controller.ts
import { Controller, Get, Post } from '@nestjs/common'
@Controller('cats')
export class CatsController {
@Post()
create(): string {
return '这个动作会创建一个新的cat'
}
@Get()
findAll(): string {
return '这个动作会返回所有的cats'
}
}
Nest为剩余的HTTP请求标准提供了相同风格的装饰器—— @Put(), @Delete(), @Patch(), @Options(), @Head(), @All()
Route wildcard 路由通配符
也支持基于路由的样式,*作为通配符可以匹配到所有字符组合。
@Get('ab*cd')
findAll() {
return '这个路由使用了通配符'
}
'ab*cd'
会匹配abcd
,ab_cd
,abcde
等等。字符?
,+
,*
,()
以及对应正则表达式的自己也可以在路由中使用。连字符-
和点.
则是字符串路径的字面意思。
Status code 状态码
响应状态码默认是200,除了POST请求是201。可以通过在处理函数层添加装饰器 @HttpCode() 去改变这种习惯。
@Post()
@HttpCode(204)
create() {
return '这个动作会添加一个新的cat'
}
Hints
HttpCode
从@nestjs/common
包引入
通常状态码不是静态的,会依赖于各种不同的情况。既然这样,你可以使用Library-specific的响应对象。
Headers 请求头
指定自定义的请求头,可以使用装饰器 @Header()或者Library-specific的响应对象(res.header())
@Post()
@Header('Cache-Control', 'none')
create() {
return '这个动作会添加一个新的cat'
}
Hints
import { Header } from '@nestjs/common
Redirection 重定向
将响应重定向到特定的url,可以使用 @Redirect()或者Library-specific的响应对象(res.redirect())
@Redirect()有一个必传入参url
,和可选入参statusCode
,statusCode
不传默认是302(Found)
@Get()
@Redirect('http://nestjs.com', 301)
有时候可能想要动态的决定HTTP状态码或者重定向的url,只要从处理函数返回一个对象即可
{
"url": string,
"statusCode": number
}
返回的值将重写传入@Redirect 参数
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' }
}
}
Route parameters 路由参数
当需要接受请求路径中一部分作为动态数据时,静态路径做不到。(例如GET /cats/1获取到id为1的cat)。定义带有参数的路由,只需要添加参数标识就可以在请求url中捕获动态的值。然后就可以在函数签名中使用@Param()访问到路由参数。
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `这个动作会返回id为${params.id}的cat`
}
我们可以接受一个id
作为参数来获取到params.id
@Get('id')
findOne(@Param('id') id): string {
return `这个动作会返回id为${id}的cat`
}
Asynchronicity 异步
数据的获取是异步的,Nest支持async函数
每个异步的函数返回一个Promise
,这意味着你可以返回一个延迟的值,Nest可以自己处理。
// cats.controller.ts
@Get()
async findAll(): Promise<any[]> {
return []
}
上面的代码完全有效,Nest路由处理函数甚至可以返回RxJs(可观测的数据流)。Nest将自动订阅资源,并流完成时获取最后一次发出的值。
// cats.controller.ts
@Get()
findAll(): Observable<any[]> {
return of([]);
}
两种方法都可以,根据需要来使用吧~
Request payloads 请求负载(数据)
前面的栗子,POST路由处理函数没有接受任何来自客户端的参数,现在来添加装饰器
@Body()
但是首先我们需要定义 DTO(Data Transfer Object)。DTO对象定义了数据将以何种形式通过网络发送,可以通过ts的interface
来定义DTO,或者class
类。有趣的是我们推荐用class
类。类是ES6标准的一部分,他们在编译之后会保存为一个真实的实体。然而ts的接口在编译期间会删除,Nest运行时并获取不到他们。这很重要,因为它为像Pipes管道这样的特性在运行时访问变量的元类型,提供了更多的可能性。
创建CreateDto类
// create-cast.dto.ts
export class CreateDto {
readonly name: string;
readonly age: number;
readonly bread: string;
}
只有三个基础的属性。然后我们可以在CatsController中使用新创建的DTO
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return '这个动作会添加一个新的cat'
}
完整案例
// cats.controlller.ts
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';
@Controller('cats')
export class CatsController {
@Post()
create(@Body() createCatDto: CreateCatDto) {
return '这个动作会添加一个新的cat';
}
@Get()
findAll(@Query() query: ListAllEntities) {
return `这个动作会返回所有的cat(limit: ${query.limit} items)`;
}
@Get(':id')
findOne(@Param('id') id: string) {
return `这个动作会返回一个id为${id}的cat`;
}
@Put(':id')
update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
return `这个动作为更新id为${id}的cat`;
}
@Delete(':id')
remove(@Param('id') id: string) {
return `这个动作会删除id为${id}的cat`;
}
}
运行
控制器定义好之后,Nest并不知道这个控制器的存在,并且也不会创建实例。 控制器通常属于一个模块Module,需要以数组的形式传进装饰器@Module()。
import { Module } from 'nestjs/common';
import { CatsController } from './cats/cats.controller';
@Module({
controllers: [CatsController],
})
export class AppModule {}
附录:Library-specific的用法
import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express'
@Controller('cats')
export class CatsController {
@Post()
craete(@Res() res: Response) {
res.status(HttpStatus.CREATED).send();
}
@Get()
findAll(@Res() res: Response) {
res.status(HttpStatus.OK).json([]);
}
}
Hints
尽可能使用Nest的Standard标准方法
后记
原文地址: docs.nestjs.com/controllers
关于本文
- 文章非复制黏贴,经浏览文档,以自己的理解进行,代码测试,手打书写。本篇为翻译+意译。
- 用作记录自己曾经学习、思考过的问题的一种笔记。
- 用作前端技术交流分享。
- 阅读本文时欢迎随时质疑本文的准确性,将错误的地方告诉我。本人会积极修改,避免文章对读者的误导。
关于我
- 是一只有梦想的肥柴。
- 觉得算法、数据结构、函数式编程、js底层原理等十分有趣的小前端。
- 志同道合的朋友请关注我,一起交流技术,在前端之路上共同成长。
- 如对本人有任何意见建议尽管告诉我哦~ 初为肥柴,请多多关照~
- 前端路漫漫,技术学不完。今天也是美(diao)好(fa)的一天( 跪了...orz