Node.js控制器Controller使用教程
作者:zhouxiaoxiao2015
Controller 控制器
控制器负责处理传入的请求并向客户返回响应。
一个控制器的目的是接收应用程序的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有一个以上的路由,不同的路由可以执行不同的动作。
为了创建一个基本的控制器,我们使用类和装饰器。装饰器将类与所需的元数据联系起来,并使 Nest 能够创建一个路由图(将请求绑定到相应的控制器)。
为了快速创建一个内置验证的 CRUD 控制器,你可以使用 CLI 的 CRUD 生成器:nest g resource [name]
路由
在下面的例子中,我们将使用@Controller()
装饰器,这是定义一个基本控制器所需要的。我们将指定一个可选的路由路径前缀为cats
。在@Controller()
装饰器中使用路径前缀,可以让我们轻松地将一组相关的路由分组,并尽量减少重复的代码。例如,我们可以选择将一组管理与客户实体互动的路由归入路由/customers
。在这种情况下,我们可以在@Controller()
装饰器中指定路径前缀customers
,这样我们就不必为文件中的每个路由重复这部分的路径。
import { Controller, Get } from "@nestjs/common"; @Controller("cats") export class CatsController { @Get() findAll(): string { return "This action returns all cats"; } }
要使用 CLI 创建一个控制器,只需执行$ nest g controller cats
命令。
findAll()
方法之前的@Get()
HTTP 请求方法装饰器,告诉 Nest 为 HTTP 请求的特定端点创建一个处理程序。端点与 HTTP 请求方法(本例中为 GET)和路由路径相对应。
什么是路由路径?处理程序的路径是通过连接控制器声明@Controller('cats')
中的cats
,以及方法装饰器@Get()
中指定的任何路径来确定的。由于我们已经为每个路由(cats)声明了一个前缀,并且没有在装饰器@Get()
中添加任何路径信息,Nest 就会将GET /cats
请求映射到findAll()的Get方法
上来。
如前所述,路径包括@Controller()
路径前缀和请求方法findAll()
装饰器@Get()
中配置的路径。例如,@Controller('customers')
的路径前缀与装饰器@Get('profile')
相结合,将产生一个GET /customers/profile
这样的路由映射请求。
在我们上面的例子中,当向该端点发出 GET 请求时,Nest 将该请求路由到我们用户定义的 findAll()方法。注意,我们在这里选择的方法名称是完全任意的。显然,我们必须声明一个方法来绑定路由,但 Nest 并不重视所选择的方法名称的任何意义。
这个方法将返回一个 200 状态代码和相关的响应,在这种情况下,它只是一个字符串。为什么会发生这种情况?为了解释,我们首先要介绍一个概念,即 Nest 采用了两种不同的选项来操作响应。
选项 | 描述 |
---|---|
Standard (recommended) | 使用这种内置方法,当请求处理程序返回一个 JavaScript 对象或数组时,它将自动被序列化为 JSON。然而,当它返回一个 JavaScript 原始类型(例如,字符串、数字、布尔值)时,Nest 将只发送值,而不尝试对其进行序列化。这使得响应处理变得简单:只需返回值,Nest 就会处理其余的事情。此外,响应的状态代码默认总是 200,除了使用 201 的 POST 请求。我们可以通过在处理程序级别添加@HttpCode(…)装饰器来轻松改变这一行为(见状态代码)。 |
Library-specific | 我们可以使用库特定的(例如 Express)响应对象,它可以使用方法处理签名中的@Res()装饰器注入(例如 findAll(@Res() response))。通过这种方法,你有能力使用该对象所暴露的本地响应处理方法。例如,在 Express 中,你可以使用 response.status(200).send()这样的代码来构造响应。 |
警告:
NEST 会检测处理程序是否使用@Res()或@Next(),表明你选择了库的特定选项。如果同时使用这两种方法,标准方法将自动禁用于该单一路由,并不再按预期工作。要同时使用这两种方法(例如,通过注入响应对象只设置 cookie/头文件,但仍将其余部分留给框架),你必须在@Res({ passthrough: true })装饰器中将 passthrough 选项设置为 true。
请求对象
处理程序经常需要访问客户端的请求细节。Nest 提供了对底层平台(默认为 Express)的请求对象的访问。我们可以通过在处理程序的签名中添加@Req()装饰器来指示 Nest 注入请求对象来访问它。
import { Controller, Get, Req } from "@nestjs/common"; import { Request } from "express"; @Controller("cats") export class CatsController { @Get() findAll(@Req() request: Request): string { return "This action returns all cats"; } }
为了利用表达式类型的优势(如上面的 request: Request 参数的例子),请安装@types/express 包。
请求对象代表了 HTTP 请求,并有请求查询字符串、参数、HTTP 头和正文的属性(在此阅读更多内容)。在大多数情况下,没有必要手动抓取这些属性。我们可以使用专门的装饰器来代替,比如@Body()
或@Query()
,这些装饰器开箱即用。下面是一个所提供的装饰器的列表,以及它们所代表的普通平台特定对象。
装饰器 | 特定对象 |
---|---|
@Request(), @Req() | 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 |
@HostParam() | req.hosts |
为了与跨底层 HTTP 平台(例如Express
和Fastify
)的类型兼容,Nest 提供了@Res()
和@Response()
装饰器。@Res()
是 @Response()
的简单别名。两者都直接暴露了底层的本地平台响应对象接口。当使用它们时,你也应该导入底层库的类型(例如,@types/express
)以充分利用。请注意,当你在方法处理程序中注入@Res()
或@Response()
时,你将 Nest 放入该处理程序的库特定模式中,并且你将负责管理响应。当这样做时,你必须通过调用响应对象(如 res.json(...)
或 res.send(...)
)来发出某种响应,否则 HTTP 服务器会挂起。
如何定义自己的装饰器,请学习这个章节
资源
早些时候,我们定义了一个方法来获取cats
的资源(GET 路由)。我们通常也想提供一个创建新记录的方法。为此,让我们创建一个 POST 方法。
import { Controller, Get, Post } from "@nestjs/common"; @Controller("cats") export class CatsController { @Post() create(): string { return "This action adds a new cat"; } @Get() findAll(): string { return "This action returns all cats"; } }
就是这么简单。Nest 为所有的标准 HTTP 方法提供装饰器。 @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), 和 @Head()
。此外,@All()
定义了一个可以处理所有这些方法的端点。
路由通配符
基于路由的模式也被支持。例如,*
被用作通配符,将匹配任何字符的组合。
@Get('ab*cd') findAll() { return 'This route uses a wildcard'; }
ab*cd
路由路径将匹配 abcd、ab_cd、abecd,等等。字符"?“、”+“、”*“和”()"可以在路径中使用,它们是对应于正则表达式的子集。连字符(-)和点(.)可以通过基于字符串的路径进行字面解释。
状态代码
如前所述,响应状态代码默认总是 200,除了 POST 请求是 201。我们可以通过在处理程序级别添加@HttpCode(...)
装饰器来轻松改变这一行为。
@Post() @HttpCode(204) create() { return 'This action adds a new cat'; }
从@nestjs/common
包导入HttpCode
通常,你的状态代码不是静态的,而是取决于各种因素。在这种情况下,你可以使用一个库特定的响应(使用@Res()注入)对象(或者,在出现错误时,抛出一个异常)。
头信息
要指定一个自定义的响应头,你可以使用@Header()
装饰器或一个库特定的响应对象(并直接调用res.header()
)。
@Post() @Header('Cache-Control', 'none') create() { return 'This action adds a new cat'; }
从@nestjs/common
包导入Header
重定向
要将一个响应重定向到一个特定的 URL,你可以使用@Redirect()
装饰器或一个库特定的响应对象(并直接调用res.redirect()
)。
@Redirect()
需要两个参数,url
和statusCode
,都是可选的。如果省略的话,statusCode
的默认值是302
。
@Get() @Redirect('https://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/' }; } }
路由参数
当你需要接受动态数据作为请求的一部分时,带有静态路径的路由将无法工作(例如,GET /cats/1 以获得 id 为 1 的猫)。为了定义带参数的路由,我们可以在路由的路径中添加路由参数令牌,以捕获请求 URL 中该位置的动态值。下面@Get()装饰器例子中的路由参数令牌展示了这种用法。以这种方式声明的路由参数可以使用@Param()装饰器进行访问,它应该被添加到方法签名中。
@Get(':id') findOne(@Param() params): string { console.log(params.id); return `This action returns a #${params.id} cat`; }
@Param()
被用来装饰一个方法参数(上面例子中的params
),并使路由参数作为该方法主体中被装饰的方法参数的属性可用。正如上面的代码所见,我们可以通过引用params.id
来访问id参数
。你也可以向装饰器传递一个特定的参数标记,然后在方法主体中直接引用路由参数的名称。
从@nestjs/common
包导入Param
@Get(':id') findOne(@Param('id') id: string): string { return `This action returns a #${id} cat`; }
子域路由
@Controller
装饰器可以接受一个host
选项,要求传入的请求的 HTTP 主机与某些特定的值相匹配。
@Controller({ host: "admin.example.com" }) export class AdminController { @Get() index(): string { return "Admin page"; } }
由于 Fastify 缺乏对嵌套路由器的支持,当使用子域路由时,应该使用(默认)Express 适配器来代替
与路由路径类似,hosts
选项可以使用令牌来捕获主机名称中该位置的动态值。下面@Controller()
装饰器例子中的主机参数令牌展示了这种用法。以这种方式声明的主机参数可以使用@HostParam()
装饰器进行访问,它应该被添加到方法签名中。
@Controller({ host: ":account.example.com" }) export class AccountController { @Get() getInfo(@HostParam("account") account: string) { return account; } }
范畴
对于来自不同编程语言背景的人来说,要知道在 Nest 中,几乎所有的东西都是在传入的请求中共享的,这可能是意想不到的。我们有一个到数据库的连接池,有全局状态的单体服务,等等。请记住,Node.js 并不遵循请求/响应的多线程无状态模型,其中每个请求都由一个单独的线程来处理。因此,使用单体实例对我们的应用程序是完全安全的。
但是,在极端情况下,基于请求的控制器生存周祁可能是所需的行为(However, there are edge-cases when request-based lifetime of the controller may be the desired behavior),例如 GraphQL 应用程序中的每个请求缓存,请求跟踪或多租户。在这里了解如何控制作用域。
异步性
我们热爱现代 JavaScript,我们知道数据提取大多是异步的。这就是为什么 Nest 支持并能很好地使用异步函数。
学习async / await
请点击这里
每个异步函数都必须返回一个 Promise。这意味着你可以返回一个延迟值,Nest 将能够自己解决。让我们来看看这个例子。
@Get() async findAll(): Promise<any[]> { return []; }
上述代码是完全有效的。此外,Nest 路由处理程序通过能够返回 RxJS 可观察流而变得更加强大。Nest 将自动订阅下面的源并获取最后一个发出的值(一旦流完成)–( Nest will automatically subscribe to the source underneath and take the last emitted value (once the stream is completed))
@Get() findAll(): Observable<any[]> { return of([]); }
上述两种方法都有效,你可以使用任何符合你要求的方法。
请求的有效载荷
我们之前的 POST 路由处理器的例子没有接受任何客户端参数。让我们通过在这里添加@Body()
装饰器来解决这个问题。
但首先(如果你使用 TypeScript),我们需要确定 DTO(数据传输对象)模式。DTO 是一个定义了数据如何在网络上发送的对象。我们可以通过使用 TypeScript 接口,或通过简单的类来确定 DTO 模式。有趣的是,我们在这里推荐使用类。为什么呢?类是 JavaScript ES6 标准的一部分,因此它们在编译后的 JavaScript 中被保留为真实的实体。另一方面,由于 TypeScript 接口在转译过程中被移除,Nest 不能在运行时引用它们。这一点很重要,因为像管道这样的功能在运行时可以访问变量的元类型,从而实现更多的可能性。
我们来创建CreateCatDto
类。
export class CreateCatDto { name: string; age: number; breed: string; }
它只有三个基本属性。此后我们可以在CatsController
中使用新创建的DTO
。
我们的 Validation Pipe 可以过滤掉那些不应该被方法处理程序接收的属性。在这种情况下,我们可以将可接受的属性列入白名单,任何不包括在白名单中的属性都会自动从结果对象中剥离。在CreateCatDto
的例子中,我们的白名单是名称、年龄和品种属性。在这里了解更多。
处理错误
这里有一个关于处理错误的单独章节(即,与异常一起工作)。
完整例子
下面是一个例子,利用几个可用的装饰器来创建一个基本的控制器。这个控制器暴露了一些方法来访问和操作内部数据。
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 "This action adds a new cat"; } @Get() findAll(@Query() query: ListAllEntities) { return `This action returns all cats (limit: ${query.limit} items)`; } @Get(":id") findOne(@Param("id") id: string) { return `This action returns a #${id} cat`; } @Put(":id") update(@Param("id") id: string, @Body() updateCatDto: UpdateCatDto) { return `This action updates a #${id} cat`; } @Delete(":id") remove(@Param("id") id: string) { return `This action removes a #${id} cat`; } }
Nest CLI 提供了一个自动生成器(示意图),自动生成所有的模板代码,以帮助我们避免做这些事情,并使开发人员的体验更加简单。在这里阅读更多关于这个功能的信息。
开始运行
随着上述控制器的完全定义,Nest 仍然不知道CatsController
的存在,因此不会创建这个类的实例。
控制器总是属于一个模块,这就是为什么我们在@Module()
装饰器中包含控制器数组。由于我们还没有定义任何其他模块,除了根AppModule
,我们将用它来介绍CatsController
。
import { Module } from "@nestjs/common"; import { CatsController } from "./cats/cats.controller"; @Module({ controllers: [CatsController], }) export class AppModule {}
我们使用@Module()
装饰器将元数据附加到模块类,Nest 现在可以很容易地反映出哪些控制器必须被安装。
库的特定方法
到目前为止,我们已经讨论了操作响应的 Nest 标准方式。操作响应的第二种方式是使用库特定的响应对象。为了注入一个特定的响应对象,我们需要使用 @Res()
装饰器。为了显示差异,让我们把CatsController
重写成以下内容。
import { Controller, Get, Post, Res, HttpStatus } from "@nestjs/common"; import { Response } from "express"; @Controller("cats") export class CatsController { @Post() create(@Res() res: Response) { res.status(HttpStatus.CREATED).send(); } @Get() findAll(@Res() res: Response) { res.status(HttpStatus.OK).json([]); } }
虽然这种方法是可行的,而且事实上通过提供对响应对象的完全控制,在某些方面确实有更大的灵活性(头文件的操作、库的特定功能等等),但是应该谨慎使用。一般来说,这种方法不那么明确,而且确实有一些缺点。主要的缺点是,你的代码变得依赖于平台(因为底层库在响应对象上可能有不同的 API),而且更难测试(你必须模拟响应对象,等等)。
另外,在上面的例子中,你失去了与依赖 Nest 标准响应处理的 Nest 功能的兼容性,如拦截器和@HttpCode() / @Header() 装饰器。为了解决这个问题,你可以将 passthrough 选项设置为 true,如下所示。
@Get() findAll(@Res({ passthrough: true }) res: Response) { res.status(HttpStatus.OK); return []; }
现在,你可以与本地响应对象进行交互(例如,根据某些条件设置 cookies 或头信息),但把其余的事情留给框架。
到此这篇关于Node.js控制器Controller使用教程的文章就介绍到这了,更多相关Node.js Controller内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!