FORMA

控制器与路由

控制器处理 HTTP 路由与入参。见 基础管道

控制器负责处理传入的请求并向客户端返回响应。通过装饰器将类和方法与特定路由绑定,NestJS 提供了丰富且直观的 API 来定义路由、获取请求参数和构造响应。

一、路由定义

1. @Controller() 与路由前缀

@Controller() 装饰器用于定义控制器类,可接受一个路径前缀,从而将一组相关路由聚合在一个路径下。

ts
import { Controller, Get } from "@nestjs/common";

@Controller("users")
export class UsersController {
  @Get()
  findAll() {
    return "This returns all users";
  }

  @Get("profile")
  getProfile() {
    return "User profile";
  }
}
  • 请求 GET /users 会调用 findAll()
  • 请求 GET /users/profile 会调用 getProfile()

2. HTTP 方法装饰器

NestJS 为每个标准 HTTP 方法提供了对应的装饰器:

装饰器HTTP 方法描述
@Get()GET获取资源
@Post()POST创建资源
@Put()PUT全量更新资源
@Patch()PATCH部分更新资源
@Delete()DELETE删除资源
@Options()OPTIONS预检请求
@Head()HEAD仅请求响应头

每个方法装饰器可以接受一个字符串参数作为路由路径(相对控制器前缀)。

ts
@Post('create')
createUser() { ... }

3. 路由参数通配符与 Restful 风格

  • 动态参数(路由参数):使用冒号 : 声明,后接参数名,通过 @Param() 获取。
ts
@Get(':id')
findOne(@Param('id') id: string) { ... }
  • 通配符:可以使用 * 匹配任意路径(仅支持末尾通配符)。
ts
@Get('files/*')
handleWildcard() { ... }
  • Restful 风格:NestJS 天然支持设计符合 REST 规范的路由,通常配合 @Get(':id')@Post()@Put(':id')@Delete(':id')

二、请求对象处理

NestJS 提供了多种装饰器来获取请求中的信息,大部分基于 Express 或 Fastify 的原生请求对象,但通过装饰器简化了访问方式。

装饰器描述示例
@Req() / @Request()获取原生请求对象@Req() request: Request
@Res() / @Response()获取原生响应对象@Res() response: Response
@Body()获取请求体,可选参数名@Body() body: CreateUserDto
@Query()获取查询参数(Query String)@Query('page') page: string
@Param()获取路由参数@Param('id') id: string
@Headers()获取请求头@Headers('authorization') auth: string
@Ip()获取客户端 IP 地址@Ip() clientIp: string
@Session()获取会话对象(需配置 session 中间件)@Session() session: Record<string, any>

注意

  • 若使用 @Res() 并手动调用 res.send(),则框架不会再自动处理返回值。此时需要自己发送完整响应。
  • 为了同时获取原生请求和部分装饰器(如 @Body),可以注入 @Req() 而不使用 @Res(),让框架继续自动序列化返回值。

示例:综合使用

ts
@Post()
create(
  @Body() createUserDto: CreateUserDto,
  @Query('token') token: string,
  @Headers('user-agent') userAgent: string,
) {
  console.log(token, userAgent);
  return this.usersService.create(createUserDto);
}

三、响应处理

1. 自动序列化

控制器方法返回的值如果是对象或数组,NestJS 会自动将之 JSON 序列化并设置 Content-Type: application/json。返回字符串或数字则会原样输出(作为纯文本)。

ts
@Get()
findAll() {
  return [{ id: 1, name: 'John' }]; // 自动转为 JSON
}

2. 状态码修饰

默认状态下,POST 请求返回 201 Created,其他请求返回 200 OK。可以通过 @HttpCode() 修改状态码。

ts
@Post()
@HttpCode(204)
create() {
  // 返回 204 No Content
}

3. 自定义响应头

使用 @Header() 添加自定义响应头(可同时使用多个)。

ts
@Get()
@Header('Cache-Control', 'no-cache')
findAll() {
  return 'data';
}

4. 重定向

@Redirect() 装饰器可以指定重定向的 URL 和状态码(默认 302)。也可以通过函数返回值动态重定向。

ts
@Get('docs')
@Redirect('https://docs.example.com', 301)
getDocs() {}

// 动态重定向
@Get('legacy')
findOld(@Query('version') version) {
  if (version === '2') {
    return { url: 'https://new-site.com/v2', statusCode: 301 };
  }
  return { url: 'https://new-site.com', statusCode: 302 };
}

5. 完全控制响应流(使用 @Res()

当需要完全控制响应(例如文件下载、流式响应、设置复杂 cookie 等)时,可以注入 @Res() 对象。

ts
import { Response } from 'express';

@Get('file')
downloadFile(@Res() res: Response) {
  const file = createReadStream('file.pdf');
  res.setHeader('Content-Type', 'application/pdf');
  file.pipe(res);
}

注意:一旦使用 @Res() 并手动发送响应,NestJS 将中断该请求的处理链(不会执行拦截器中的 map 等操作)。如果仍希望让框架处理响应(例如只想要修改部分响应头),可以注入 @Res({ passthrough: true })。在 Nest 9+ 中支持该选项。

ts
@Get()
find(@Res({ passthrough: true }) res: Response) {
  res.setHeader('X-Custom', 'Value');
  return { data: 'hello' }; // 仍然由框架自动序列化
}

四、最佳实践

  • 避免混合使用 @Res() 和自动返回:除非使用 { passthrough: true },否则选择其中一种方式。
  • 使用 DTO 配合 @Body():通过 class-validator 进行数据验证。
  • 路由参数装饰器顺序:不影响功能,但通常按 @Param()@Query()@Body() 排列。
  • 全局前缀:可以在 app.setGlobalPrefix('api') 设置全局路由前缀,与控制器前缀叠加。

总结表

场景使用的装饰器/工具
定义路径前缀@Controller('prefix')
定义方法及子路径@Get('subpath')@Post()
获取请求体@Body()
获取查询参数@Query('name')
获取路由参数@Param('id')
获取请求头@Headers('key')
获取客户端 IP@Ip()
获取原生请求对象@Req()
获取原生响应对象(完全控制)@Res()
修改状态码@HttpCode()
添加响应头@Header()
重定向@Redirect() 或返回 { url, statusCode }
流式/文件响应@Res() 手动 pipe

通过合理使用这些装饰器,可以快速构建规范的 RESTful API,同时保持代码简洁和类型安全。

参考文献

以下链接在编写时均可正常访问:

资料说明
NestJS 文档官方
Controllers本章主题
Request lifecycle执行顺序