通过nest new project-name 新建一个项目,我们在src/app.controller.ts里可以看到如下代码:
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
第六行constructor(private readonly appService: AppService) {}我们将在Providers一节讲解,这里先忽略:
第四行通过@Controller()装饰器,我们可以捕获前端传过来的所有以/开头的路由,也就是@Controller装饰器定义了一组路由的处理方式,这里的一组路由,就是以/开头的路由。如果是@Controller('app'),那么定义的分组路由就是以/app开头。
之后的getHello里的@Get()中传参为空,意味着捕获的路由是/。@Controller定义了一组路由之后,其内部的类里需要去捕获这组路由下的子路由,包括分组路由本身。
我们通过nest start --watch启动服务后,在浏览器输入localhost:3000,即可看到hello world的输出。这个输出的逻辑同样放在Providers一节讲解。
1. Get请求
通过@Get()装饰器,我们可以捕获指定的get请求。修改@Controller()为@Controller('app'),即定义的分组路由为/app,然后修改@Get()装饰器为@Get('a')。
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('a')
getHello(): string {
return this.appService.getHello();
}
}
首先通过@Controller()接收所有以/app开头的请求,然后在getHello方法中添加装饰器@Get('a')装饰器,这个装饰器传参为a,意味捕获的get请求路由为/app/a。
2. 其他请求方式
刚才我们学会了如何处理Get请求,其他的诸如Post请求、Put请求也是一样的用法。比如Post请求:
// app.controller.ts
import {Controller, Get, Post} from '@nestjs/common';
@Controller('app')
export class AppController {
@Post('b')
create(): string {
return '处理了一个post请求。';
}
@Get('a')
getHello():string {
return '处理了一个get请求';
}
}
在Postman中输入localhost:3000/app/b,将会被定义了@Post('b')装饰器的create方法所捕获,因此会返回处理了一个post请求。
就是这么简单。Nest为所有标准HTTP方法提供装饰器: @Get() 、 @Post() 、 @Put() 、 @Delete() 、 @Patch() 、 @Options() 和 @Head() 。此外, @All() 装饰器将捕获未被处理的请求。请看下面代码:
// app.controller.ts
import { All, Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('b')
create(): string {
return '处理了一个post请求。';
}
@Get('a')
getHello():string {
return '处理了一个get请求';
}
@All()
getAll(): string {
return 'All';
}
}
这段代码里添加了一个@All()装饰器装饰的getAll方法。我们之前的@Post('b')和@Get('a')分别捕获了/app/p的post请求,以及/app/a的get请求,但是就是没有处理/app本身,而/app这个请求却是被@Controller(‘app’)定义过的,这时候,就是@All()装饰器的作用,它能捕获@Controller定义的未被处理的分组路由。
3. 获取请求的详细信息
上面我们学习了怎么处理不同的请求方式,接下来我们继续学习,怎么获取请求方式里传递的参数。
在处理GET请求的函数入参中,我们可以通过装饰器@Req注入Request实例,获得请求对象的信息。
// app.controller.ts
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('app')
export class CatsController {
@Get('a')
getHello(@Req() req: Request): {} {
const { headers, query, params, body, method, url } = req;
return { headers, query, params, body, method, url };
}
}
为了使用
express类型,(如上面的req: Request参数示例),需要安装@types/express包。
上图我用了谷歌的
JSON-handle插件格式化了返回的报文样式。
在这个例子中,我们在req对象中获取了headers, query, params, body, method, url等属性,并返回给了前端。
不过对于常用的GET请求和POST请求来说,最常用的属性还是query和body,那每次使用这两个属性的时候都要从req中解构不是太麻烦了吗?
别急,Nest为我们提供了更简便的装饰器@Query和@Body,通过这两个装饰器,我们就能快速获取请求中的query属性和body属性。
// app.controller.ts
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('a')
getHello(@Query() query: {}): {} {
return query;
}
@Post('b')
create(@Body() body: {}): {} {
return body;
}
}
上述代码分别验证了/app/a的GET请求以及/app/b的POST请求,并分别用@Query()装饰器和@Body()装饰器获得了对应的参数和请求体。
类似的装饰器还有以下这些:
| 装饰器 | 代表的对象 |
|---|---|
@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 |
@Req()是@Request()的别名,@Res()是@Response()的别名。
其中,需要留意的是@Ip()和@Param()装饰器,这两个也是处理GET请求,因为在GET请求中,还有一种传参方式是路径传参。
// app.controller.ts
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('a/:idNum')
getHello(@Param() param: {}, @Param('idNum') id: string): {} {
return { param: param, idNum: id };
}
}
我们在原先/app/a的GET请求后追加了idNum参数,用@Param(‘idNum’)装饰器取出参数idNum并赋值给变量id,之后,我们返回param参数和idNum参数。
4. 修改状态码
默认返回成功的状态码是200,如果我们想定制返回的状态码的话,Nest也提供了简便的装饰器@HttpCode()来手动修改状态码。我们修改/app/b的路由请求,为create方法添加@HttpCode(202)的装饰器:
// app.controller.ts
import { Body, Controller, HttpCode, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('b')
@HttpCode(202)
create(@Body() body: {}): {} {
return `Accepted: ${JSON.stringify(body)}`;
}
}
然后我们用Postman调用POST请求/app/b,就会看到返回的状态码变成了202:
如果需要更复杂的判断逻辑,可以通过@Res()注入响应对象来修改响应状态。我们在第6小节介绍。
5. 指定响应头
与@HttpCode装饰器类似的,Nest也提供了@Header装饰器来指定响应头:
// app.controller.ts
import { Body, Controller, Header, HttpCode, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('b')
@Header('Cache-Control', 'no-cache123')
create(@Body() body: {}): {} {
return `Accepted: ${JSON.stringify(body)}`;
}
}
在上述代码中,我们为/app/b的post请求添加了@Header('Cache-Control', 'no-cache123')装饰器,这里的用法是@Header(key, value),当然这个no-cache123是故意这么写的,为的就是让大家明确的知道这是我们自己添加的响应头。
如果需要定义多个响应头,可以多次使用@Header()装饰器:
// app.controller.ts
import { Body, Controller, Header, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('b')
@Header('Cache-Control', 'no-cache123')
@Header('Content-Type', 'application/json123')
create(@Body() body: {}): {} {
return `Accepted: ${JSON.stringify(body)}`;
}
}
然后我们用Postman调用/app/b的POST请求,便能看到我们自定义的响应头了:
另外,还有一个特殊的响应头装饰器@Redirect(),这个装饰器是专门用来做重定向的:
// app.controller.ts
import { Body, Controller, Get, Redirect } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('a')
@Redirect('https://docs.nestjs.com', 302)
create(@Body() body: {}): {} {
return `Accepted: ${JSON.stringify(body)}`;
}
}
我们去掉了@Header()装饰器,将/app/b的POST请求改成了/app/a的GET请求,然后添加@Redirect()装饰器,指定重定向后的路径以及响应码,接着,我们在浏览器里输入localhost:3000/app/a测试,发现/app/a的GET请求已经被重定向到指定地址了:
如果需要更复杂的判断逻辑,可以通过@Res()注入响应对象,然后通过res.set({xxx: xxx})来修改响应头。
6. 使用@Res()完全控制响应对象
以上就是Nest提供的一些简便的响应头装饰器,细心的同学可能会发现,@Header装饰器只能用于简单的逻辑,如果我需要根据不同的条件返回不同的响应头,这时候@Header()装饰器就有些捉襟见肘了,此时,我们就需要用@Res()装饰器注入Response实例,然后再根据不同的条件用Response实例返回不同的响应,以此提供更细粒度的控制:
// app.controller.ts
import { Body, Controller, Post, Res } from '@nestjs/common';
import { AppService } from './app.service';
import { Response } from 'express';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('b')
create(@Body() body: { key: string }, @Res() res: Response) {
const { key } = body;
if (key === 'value1') {
res.set({ 'custom-header': key });
res.status(200).send('This is value1');
} else {
res.set({ 'custom-header': key });
res.status(202).send('This is other');
}
}
}
在这个示例代码中,我们引入了@Res()装饰器以及Response类型,并将Response实例注入到了res属性上,在create方法内部,我们先取出body参数里的key属性,根据不同的key值,用res.set方法设置了不同的响应头,并返回了不同的状态码和内容。
现在,让我们用Postman测试一下:
首先是传参为{key: 'value1'}的情况:
根据上图,当传参为{key: 'value1'}时,返回的响应体和响应头都验证了我们走的是if的条件分支。
因此,可以确定的是,当传参为{key: 'other'}的情况,走的便是我们的else分支。
如此,我们便通过@Res()装饰器注入的Respose实例,实现了对于响应头更加精细的控制。
不过,使用@Res()时需要注意一下几点:
- 使用
@Res()装饰器的方法不能有返回值,否则 Nest 会基于返回值生成响应,导致冲突。 - 使用
@Res()注入响应对象时,会导致响应对象被完全控制,必须要手动模拟响应,否则HTTP服务将挂起。也就是说如果没有send()方法,会导致服务被挂起。