通过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()
方法,会导致服务被挂起。