控制器层负责处理传入的请求, 并返回对客户端的响应。
为了创建一个基本的控制器,我们必须将元数据附加到类中。Nest 知道如何映射我们的控制器到相应的路由。
下面我们来定义一个 UsersController 控制器,如果使用 Nest CLI 的话,可以在命令行执行以下命令:
$ nest generate controller users
该命令执行后,命令行会输出以下信息:
CREATE /src/users/users.controller.spec.ts (478 bytes)
CREATE /src/users/users.controller.ts (99 bytes)
UPDATE /src/app.module.ts (386 bytes)
通过观察以上输出信息,我们知道该命令 “幕后” 主要做了以下两件事:
users.controller.spec.ts 文件是用于写测试用例,这里我们就不分析了。我们来分析一下 users.controller.ts:
import { Controller } from '@nestjs/common';
@Controller('users')
export class UsersController {}
在上面的示例中,我们在 UsersController 类上使用了 @Controller('users')
装饰器。users
是每个类中注册每个路由时的可选前缀。使用前缀的目的是为了避免所有路由共享通用的前缀时出现冲突的情况。
接下来我们来继续分析一下 src/app.module.ts 更新了哪些内容:
更新前:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
更新后:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersController } from './users/users.controller';
@Module({
imports: [],
controllers: [AppController, UsersController],
providers: [AppService],
})
export class AppModule {}
通过比较更新前和更新后的文件,我们知道 UPDATE /src/app.module.ts
所执行的操作就是在 AppModule 根模块注册新建的 UsersController
。现在我们来简单总结一下,在 Nest.js 中自定义控制器的流程:
接下来我们来更新一下 UsersController,添加相关代码具体如下:
import { Controller, Get } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
getAllUsers() {
return [{ name: 'semlinker', age: 32 }];
}
@Get(':id')
getUser() {}
}
@Get
方法装饰器,用于告诉 Nest 创建此路由路径的端点,并将每个的请求映射到相应的处理程序。由于在定义 UsersController 控制器时,我们使用了 users
路由前缀,因此当我们发起 /users
请求时,会调用 getAllUsers
方法。
更新完 UsersController 类,我们来测试一下,首先重新启动一下服务器:
$ npm run start
然后我们在浏览器打开 http://localhost:3000/users
,正常情况下,你将看到以下的输出信息:
[{"name":"semlinker","age":"32"}]
在某些情况下,我们需要获取请求对象,这时我们可以利用 Nest 的 @Req
装饰器,将请求对象注入处理程序。请求对象会包含查询参数,HTTP 请求头和请求体等属性。但在大多数情况下,我们不必手动获取它们,因为 Nest 已经为我们提供了对应的装饰器,对应的关系如下:
@Request() | req |
---|---|
@Response() | res |
@Next() | next |
@Session() | req.session |
@Param(param?: string) | req.params / req.params[param] |
@Body(param?: string) | req.body / req.body[param] |
@Query(param?: string) | req.query / req.query[param] |
@Headers(param?: string) | req.headers / req.headers[param] |
下面我们来更新一下 UsersController
:
import { Controller, Get, Req, Res, Param, HttpStatus } from '@nestjs/common';
@Controller('users')
export class UsersController {
users = [{ id: 1, name: 'semlinker', age: 32 }];
@Get()
getAllUsers(@Res() res) {
res.status(HttpStatus.OK).json(this.users);
}
@Get(':id')
getUser(@Req() req, @Res() res, @Param('id') id) {
console.log(`Request path: ${req.path}`);
console.log(`Resource id: ${id}`);
res.status(HttpStatus.OK).json(this.users.find(user => user.id == id));
}
}
以上示例,我们使用 @Res()
装饰器来获取响应对象,然后设置响应状态码和响应数据。同时也介绍了使用 @Req()
和 @Param()
装饰器来分别获取请求对象和路由参数。
在介绍如何处理 Post 请求获取请求体前,我们先来介绍一下 DTO(数据传输对象)。DTO 是一个定义如何通过网络发送数据的对象。我们可以使用 TypeScript 接口或简单的类来定义对象。但是我们建议在这里使用类。这是为什么呢?因为类是 JavaScript ES6 标准的一部分,它们只是简单的函数。然而 TypeScript 定义的接口在编译过程中会被移除,导致 Nest 不能引用它们。
现在我们来创建 CreateUserDto
:
export class CreateUserDto {
readonly name: string;
readonly age: number;
}
CreateUserDto 类有两个属性。 所有这些都被标记为只读,因为我们应该尽可能使我们的功能尽可能不被污染。
创建完 CreateUserDto 类之后,我们来更新一下 UsersController 类,为它新增一个方法用于处理新增用户:
import { Controller, Get, Req, Res, Param, Post, Body, HttpStatus } from '@nestjs/common';
import { CreateUserDto } from './create-user.dto';
@Controller('users')
export class UsersController {
users = [{ id: 1, name: 'semlinker', age: 32 }];
@Get()
getAllUsers(@Res() res) {
res.status(HttpStatus.OK).json(this.users);
}
@Get(':id')
getUser(@Req() req, @Res() res, @Param('id') id) {
console.log(`Request path: ${req.path}`);
console.log(`Resource id: ${id}`);
res.status(HttpStatus.OK).json(this.users.find(user => user.id == id));
}
@Post()
createUser(@Res() res, @Body() createUserDto: CreateUserDto) {
console.dir(createUserDto);
res.status(HttpStatus.CREATED).send();
}
}
在上面代码中,我们通过 @Body()
装饰器获取 Post 请求体的内容,然后通过 @Res()
获取响应对象,进而设置响应状态码。好的,现在我们来验证一下,看看是否能正常处理 Post 请求。
首先运行以下命令启动我们的应用程序:
$ npm run start
因为我使用的是 Visual Studio Code,所以我将使用 REST Client 这款功能强大的 HTTP Client 插件,来发送 HTTP 请求。对于其他的小伙伴来说,也可以使用其它的 HTTP Client,如 Postman、Paw 或 Fiddler 等。
接着我们在项目根目录下创建一个 app.http
文件并输入以下内容:
POST http://127.0.0.1:3000/users HTTP/1.1
content-type: application/json
{
"name": "lolo",
"age": 3
}
然后打开 app.http
文件,右键选择 Send Request
菜单,如果一切正常的话,终端会输出以下消息:
{ name: 'lolo', age: 3 }
此外 REST Client 还会为我们自动打开一个新的窗口,用于显示 HTTP 响应报文:
HTTP/1.1 201 Created
X-Powered-By: Express
Date: Mon, 17 Sep 2018 05:59:40 GMT
Connection: keep-alive
Content-Length: 0
前面我们已经介绍了 @Get()
、@Post()
、@Req()
、 @Param()
、 @Body()
和 @Res()
装饰器,下面我们再来介绍 @HttpCode()
和 @Header()
这个两个装饰器。顾名思义,它们分别用来设置状态码和响应头,使用示例如下:
@Post()
@HttpCode(201)
@Header('Cache-Control', 'none')
createUser(@Body() createUserDto: CreateUserDto) {
console.dir(createUserDto);
return { success: true };
}
更新完上述代码,我们重启一下服务器,然后再使用 REST Client 发送请求,之后我们再来查看 HTTP 响应结果:
HTTP/1.1 201 Created
X-Powered-By: Express
Cache-Control: none
Content-Type: application/json; charset=utf-8
Content-Length: 16
ETag: W/"10-oV4hJxRVSENxc/wX8+mA4/Pe4tA"
Date: Mon, 17 Sep 2018 08:36:26 GMT
Connection: keep-alive
{
"success": true
}
最后我们来介绍 Nest 支持的一个很强大的特性 —— Async / await。
Nest 不但支持异步函数,而且还支持 RxJS Observable 流。这对于喜欢 Angular 或 RxJS 的开发者来说,是一个福音。下面我们来简单看一下示例:
@Get()
async findAll(): Promise<any[]> {
return [];
}
@Get()
findAll(): Observable<any[]> {
return of([]);
}