控制器 
控制器(controller)是负责处理传入的请求以及向客户端返回响应。控制器(controller)一般是由很多路由(前端习惯性称为接口)组成,根据请求类型(get、post、patch...)和路由名分发到各个服务,并且处理服务返回的结果或者数据,同时响应给客户端。
控制器(controller)的结构为:
@Controller("cats")
export class CatController {
  // ... 路由组成
}@Controller("cats")
export class CatController {
  // ... 路由组成
}当然可以通过nest.js的 cli 工具可以快速的创建控制器(controller):
nest g co <控制器名称>nest g co <控制器名称>可以看到控制器(controller)是被装饰器@Controller()所修饰的类,能接收一个参数,该参数会以路径的方式映射到 url 中。比如上述的@Controller("cats"),假设当前nest.js服务的地址为localhost:3000,那么不设置全局前缀的前提下,访问localhost:3000/cats即可访问该控制器(controller)的路由。
注意
Nest.js是由模块、控制器、服务组成,控制器要生效必须需要在对应模块的controllers数组注入或者模块在另一个模块的import完成注入。
@Module({
  controllers: [controllers],
  providers: [],
})@Module({
  controllers: [controllers],
  providers: [],
})路由 
“路由”不严谨的说就是前后端联调中所说“接口”,要想访问指定的路由,只要访问特定的控制器前缀以及对应路由名即可(如果有指定的话)。举个例子现在搓一个获取“猫咪列表”的get的路由:
import { Get, Controller } from "@nest/common";
@Controller("cats")
export class CatController {
  @Get()
  getCats() {
    return "This is route getCats";
  }
}import { Get, Controller } from "@nest/common";
@Controller("cats")
export class CatController {
  @Get()
  getCats() {
    return "This is route getCats";
  }
}然后启动nest.js的服务,在 url 中输入(假设服务启动后的端口为 3000)localhost:3000/cats就可以发现页面输出打印的信息。 当然这是没有给路由一个“前缀”(或者命名更恰当)的情况,控制器自上往下找到能匹配的路由,找到后就使用改路由的逻辑并且返回给客户端,所以上述才能通过控制器的前缀就访问内部的接口。这种设计往往是不友好的,因为后续可能会存在:id等等类型的路由,为此@Get等请求类型的装饰器支持传入一个字符串来确定该路由的前缀。
import { Get, Controller } from "@nest/common";
@Controller("cats")
export class CatController {
  @Get("list")
  getCats() {
    return "This is route getCats";
  }
}import { Get, Controller } from "@nest/common";
@Controller("cats")
export class CatController {
  @Get("list")
  getCats() {
    return "This is route getCats";
  }
}现在访问localhost:3000/cats/list才能正确的访问该get请求。
路由还支持通配符,只不过这个可能会比较少用,列入把@Get('list') 改为 @Get('li*s'), 那么现在 li*s将匹配li_s、liinfs等等,字符 ? 、+ 、 * 以及 () 是它们的正则表达式对应项的子集。连字符(-) 和点(.)按字符串路径逐字解析。
提示
控制器(controller)当返回基本类型(String、Boolean、Number)的时候,它是不会做任何处理的,Nest.js只返回值。 但当返回类型是一个数组或者对象时,Nest.js就会去序列化成JSON,Nest.js这样做的目的是为了开发更加简单和方便,开发者只关心返回值,其他交给Nest.js。
REST API 
REST API(Representational State Transfer Application Programming Interface)是一种遵循 REST 架构风格的应用程序编程接口。REST 是一种设计网络应用程序的模式,它强调简单性、可扩展性和可读性,利用现有的 HTTP 协议标准,使得 Web 服务更加结构化和可预见。
细节部分这里可以参考阮一峰老师的 RESTful API 最佳实践。
这里可以简单概述几个点:
- api 的命名必须为名词,诸如getlist,seeList等这些都是不符合规范的。
- 报错的code码有明确的含义。
- 请求类型应该根据路由的作用而做更精细的细分。
比如:
- Get 获取/加载资源
- Post 创建资源
- Put 更新资源
- Delete 删除资源
- Patch 更新资源,通常泛指更新一部分
获取参数 
前端向后端发起接口请求必然会携带参数,Nest.js这里提供了非常多开箱即用的装饰器,来快速的获取request或其中的内容。比如下面例子中获取request:
import { Get, Controller, Request } from "@nest/common";
@Controller("cats")
export class CatController {
  @Get("list")
  getCats(@Request() req) {
    return "This is route getCats";
  }
}import { Get, Controller, Request } from "@nest/common";
@Controller("cats")
export class CatController {
  @Get("list")
  getCats(@Request() req) {
    return "This is route getCats";
  }
}当然一般拿request都是获取其中传入的参数或者一些特定的内容,这里不必req.body获取传入参数等操作, Nest.js有相当简单且方便的装饰器:
参数的类型推断 
Nest的参数类型推断和前端的有一定的区别,它是使用类来做为DTO(数据传输格式),为什么不和前端一样使用interface,Nest的官方文档是这样说明的,“首先(如果您使用 TypeScript),我们需要确定 DTO(数据传输对象)模式。DTO 是一个对象,它定义了如何通过网络发送数据。我们可以通过使用 TypeScript 接口(Interface)或简单的类(Class)来定义 DTO 模式。有趣的是,我们在这里推荐使用类。为什么?类是 JavaScript ES6 标准的一部分,因此它们在编译后的 JavaScript 中被保留为实际实体。另一方面,由于 TypeScript 接口在转换过程中被删除,所以 Nest 不能在运行时引用它们。这一点很重要,因为诸如管道(Pipe)之类的特性为在运行时访问变量的元类型提供更多的可能性。”
这也是后续知识面需要额外习惯的一点(特别是TypeOrm),这里定义传入数据格式使用类来代替interface。
具体用法只需要在装饰器修饰的参数后: dto类型定义即可。
举个例子:
@Body() body: bodyDto@Body() body: bodyDto@Query 装饰器 
@Query主要获取路由中?后的参数,比如现在路由为localhost:3000/cats/list?type=布偶猫,需要获取参数 type:
import { Get, Controller, Query } from "@nest/common";
import { queryDto } from "./dto/create.dto"; // 假设在 create.dto.ts 有 queryDto
@Controller("cats")
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto  //确定dto) {
    return "This is route getCats" + `as ${query.type}`; // ---> This is route getCats as 布偶猫
  }
}import { Get, Controller, Query } from "@nest/common";
import { queryDto } from "./dto/create.dto"; // 假设在 create.dto.ts 有 queryDto
@Controller("cats")
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto  //确定dto) {
    return "This is route getCats" + `as ${query.type}`; // ---> This is route getCats as 布偶猫
  }
}也可以往@Query()装饰器传入一个字符串来快捷匹配query内的参数:
import { Get, Controller, Query } from "@nest/common";
import { queryDto } from "./dto/create.dto"; // 假设在 create.dto.ts 有 queryDto
@Controller("cats")
export class CatController {
  @Get("list")
  getCats(@Query("type") type: queryDto) {
    return "This is route getCats" + `as ${type}`; // ---> This is route getCats as 布偶猫
  }
}import { Get, Controller, Query } from "@nest/common";
import { queryDto } from "./dto/create.dto"; // 假设在 create.dto.ts 有 queryDto
@Controller("cats")
export class CatController {
  @Get("list")
  getCats(@Query("type") type: queryDto) {
    return "This is route getCats" + `as ${type}`; // ---> This is route getCats as 布偶猫
  }
}@Param() 装饰器 
有时候路由需要支持动态参数,比如/cats/list/1,需要获取后面的1,那么这个时候就可以使用 @Param()装饰器。
import { Get, Controller, Query } from "@nest/common";
import { paramDto } from "./dto/create.dto";
@Controller("cats")
export class CatController {
  @Get("list/:id")
  getCats(@Param() Param: paramDto) {
    return "This is route getCats" + `as ${Param.id}`; // ---> This is route getCats as 1
  }
}import { Get, Controller, Query } from "@nest/common";
import { paramDto } from "./dto/create.dto";
@Controller("cats")
export class CatController {
  @Get("list/:id")
  getCats(@Param() Param: paramDto) {
    return "This is route getCats" + `as ${Param.id}`; // ---> This is route getCats as 1
  }
}同样的装饰器也可以传入一个字符串快速的匹配参数。
import { Get, Controller, Query } from "@nest/common";
@Controller("cats")
export class CatController {
  @Get("list/:id")
  getCats(@Param("id") id) {
    return "This is route getCats" + `as ${id}`; // ---> This is route getCats as 1
  }
}import { Get, Controller, Query } from "@nest/common";
@Controller("cats")
export class CatController {
  @Get("list/:id")
  getCats(@Param("id") id) {
    return "This is route getCats" + `as ${id}`; // ---> This is route getCats as 1
  }
}@Body() 装饰器 
@Body()装饰器算是最经常使用的(毕竟前后端联调没可能把参数都是 url 传参),这里为了高效的类型推断,需要对@Body()修饰的参数给予一个类(上述参数的类型推断有说明原因),使用方法也非常简单,假设现在请求载荷为{name: '白'}:
import { Get, Controller, Query, Body } from "@nest/common";
import { bodyDto } from "./dto/body.dto";
@Controller("cats")
export class CatController {
  @Get("list")
  getCats(@Body() body: bodyDto) {
    return "This is route getCats" + `as ${body.name}`; // ---> This is route getCats as 白
  }
}import { Get, Controller, Query, Body } from "@nest/common";
import { bodyDto } from "./dto/body.dto";
@Controller("cats")
export class CatController {
  @Get("list")
  getCats(@Body() body: bodyDto) {
    return "This is route getCats" + `as ${body.name}`; // ---> This is route getCats as 白
  }
}如果同时需要接收param动态参数呢? 那么只需要加上逗号,,在后面再定义即可。假设现在请求载荷为{name: '白'}, 动态参数为/cats/list/1:
import { Get, Controller, Query, Body } from "@nest/common";
import { bodyDto } from "./dto/body.dto";
@Controller("cats")
export class CatController {
  @Get("list/:id")
  getCats(@Body() body: bodyDto, @Query('id') : id) {
    return "This is route getCats" + `as ${body.name} - ${id}`; // ---> This is route getCats as 白 - 1
  }
}import { Get, Controller, Query, Body } from "@nest/common";
import { bodyDto } from "./dto/body.dto";
@Controller("cats")
export class CatController {
  @Get("list/:id")
  getCats(@Body() body: bodyDto, @Query('id') : id) {
    return "This is route getCats" + `as ${body.name} - ${id}`; // ---> This is route getCats as 白 - 1
  }
}状态码 
Nest.js默认非post请求响应的状态码为200,post响应为201。但它支持根据实际需求进行自定义的返回(原则上尽量不改,遵循框架约束)。这里只需要使用@HttpCode装饰器,它一样也是在@nest/common导出,它接受一个数值类型,这个数值就是需要自定义返回的状态码。
import { Get, Controller, Query, HttpCode } from "@nest/common";
import { queryDto } from "./dto/create.dto";
@Controller("cats")
@HttpCode(203) // 让它响应的code为203
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto) {
    return "This is route getCats" + `as ${query.type}`; // ---> This is route getCats as 布偶猫
  }
}import { Get, Controller, Query, HttpCode } from "@nest/common";
import { queryDto } from "./dto/create.dto";
@Controller("cats")
@HttpCode(203) // 让它响应的code为203
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto) {
    return "This is route getCats" + `as ${query.type}`; // ---> This is route getCats as 布偶猫
  }
}自定义请求头类型 
当需要自定义请求头的时候,就需要使用@Header装饰器,该装饰器一样从@nest/common引入。
import { Get, Controller, Query, Header } from "@nest/common";
import { queryDto } from "./dto/create.dto";
@Controller("cats")
@Header("Cache-Control", "none")
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto) {
    return "This is route getCats" + `as ${query.type}`; // ---> This is route getCats as 布偶猫
  }
}import { Get, Controller, Query, Header } from "@nest/common";
import { queryDto } from "./dto/create.dto";
@Controller("cats")
@Header("Cache-Control", "none")
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto) {
    return "This is route getCats" + `as ${query.type}`; // ---> This is route getCats as 布偶猫
  }
}重定向 
当需要把该路由重定向到另一个 url 中,这时候就需要使用@Redirect装饰器,该装饰器接受两个参数,第一个参数为 url(string),需要指定该路由重定向去哪里。另一个参数为 startCode 状态码,它是可选的,不传的时候默认为 301。
import { Get, Controller, Query, Header } from "@nest/common";
import { queryDto } from "./dto/create.dto";
@Controller("cats")
@Redirect("http://nestjs.com", 303)
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto) {
    return "This is route getCats" + `as ${query.type}`; // ---> This is route getCats as 布偶猫
  }
}import { Get, Controller, Query, Header } from "@nest/common";
import { queryDto } from "./dto/create.dto";
@Controller("cats")
@Redirect("http://nestjs.com", 303)
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto) {
    return "This is route getCats" + `as ${query.type}`; // ---> This is route getCats as 布偶猫
  }
}有时候,需要根据逻辑的情况自定义重定向。那么这个时候只要满足type = {url: string, startCode: number}复写重定向即可。
import { Get, Controller, Query, Header } from "@nest/common";
import { queryDto } from "./dto/create.dto";
@Controller("cats")
@Redirect("http://nestjs.com", 303)
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto) {
    return {
      url: "http://nestjs.com/${query.id}",
      startCode: 301,
    }; //复写 @Redirect
  }
}import { Get, Controller, Query, Header } from "@nest/common";
import { queryDto } from "./dto/create.dto";
@Controller("cats")
@Redirect("http://nestjs.com", 303)
export class CatController {
  @Get("list")
  getCats(@Query() query: queryDto) {
    return {
      url: "http://nestjs.com/${query.id}",
      startCode: 301,
    }; //复写 @Redirect
  }
} zerone
zerone