控制器与路由:AppController
学习目标
- 理解 Controller 在 MVC 分层中的职责
- 掌握
@Controller()与@Get()装饰器如何定义路由 - 理解构造函数注入的写法与原理
- 学会扩展更多路由(路径、参数、请求方法)
涉及核心文件
src/app.controller.tssrc/app.service.ts(被依赖方,第 5 章详讲)
一、完整代码与逐行讲解
ts
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service.js";
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}第 1 行:从 @nestjs/common 导入两个装饰器。Nest 的 API 几乎全部是装饰器形式。
第 4 行:@Controller()——把这个类标记为控制器。括号里可以传路径前缀:
ts
@Controller() // 无前缀,路由就是方法上定义的路径
@Controller('users') // 前缀 /users,方法上的路径拼在其后第 6 行:构造函数注入(本行是 Nest 最精髓的语法)
ts
constructor(private readonly appService: AppService) {}一行代码同时完成三件事:
- TS 参数属性语法:
private readonly appService: AppService等价于声明成员变量private readonly appService: AppService;并在构造函数里this.appService = appService;——这是 TypeScript 独有的简写; - 声明依赖:构造签名告诉 Nest"我要一个 AppService 实例";
- 触发 DI:由于
tsconfig.json开了emitDecoratorMetadata,编译时会把参数类型(AppService)作为元数据发射出来。Nest 实例化 AppController 时读取该元数据,从 DI 容器取出 AppService 实例传入——你不用 new 任何东西。
为什么写 readonly?因为依赖在构造时注入后不该被替换,这是防御性的好习惯。
第 8-9 行:@Get() 路由方法
ts
@Get()
getHello(): string {
return this.appService.getHello();
}@Get()把getHello注册为GET /的处理函数(无参数 = 根路径;@Get('info')则是GET /info)。- 方法体只做一件事:委托给 service。控制器保持"薄",业务逻辑都在服务层——这是 Nest 强调的分层。
- 返回值即响应体:
return 'Hello World!'→ Nest 自动序列化并以 200 返回。返回对象会自动 JSON 化;返回 Promise/Observable 也支持。
二、关键概念:路由是如何被注册的
装饰器本身不改变方法行为,它只是记录元数据。启动时:
Nest 扫描 AppController 原型
→ 发现 getHello 方法上有 @Get() 留下的元数据(path='/', method='GET')
→ 结合 @Controller() 的前缀('')
→ 向 Express 注册:app.get('/', (req, res) => controller.getHello() 的包装)所以访问 GET / 时,本质是 Express 调用了包装过的 getHello()。
三、关键概念:常用 HTTP 装饰器速查
| 装饰器 | 作用 | 示例 |
|---|---|---|
@Get() / @Post() / @Put() / @Delete() / @Patch() | 绑定 HTTP 方法 | @Post('create') |
@Param('id') | 取路径参数 | GET /users/42 → id='42' |
@Query('page') | 取查询参数 | ?page=2 |
@Body() | 取请求体 | POST 的 JSON body |
@Headers('x-token') | 取请求头 | |
@HttpCode(204) | 自定义状态码 |
四、动手实践
实践 1:加一个带前缀和参数的路由
把 app.controller.ts 改为:
ts
import { Controller, Get, Param, Query } from "@nestjs/common";
import { AppService } from "./app.service.js";
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("hello/:name")
greet(@Param("name") name: string, @Query("lang") lang?: string): string {
return lang === "en" ? `Hello, ${name}!` : `你好,${name}!`;
}
}启动后验证:
bash
curl http://localhost:3000/hello/张三
# → 你好,张三!
curl "http://localhost:3000/hello/张三?lang=en"
# → Hello, 张三!实践 2:体会返回值序列化
新增一个返回对象的方法:
ts
@Get('info')
getInfo() {
return { name: 'nest-typescript-starter', time: new Date().toISOString() };
}访问 /info,观察响应自动变成了 JSON,且 Content-Type: application/json——Nest 根据返回值类型自动处理序列化。
实践 3:制造 404
访问一个未定义的路由如 curl http://localhost:3000/nothing,观察 Nest 内置的 404 JSON 响应结构:
json
{ "message": "Cannot GET /nothing", "error": "Not Found", "statusCode": 404 }这说明框架默认接管了"路由未匹配"的情况。
本章小结
- Controller 职责单一:定义路由、解析请求、调用 Service、返回结果。
@Controller(前缀)+@Get(路径)拼出完整路由;返回值自动序列化为响应。constructor(private readonly appService: AppService)一行完成属性声明与依赖注入声明。- 控制器"薄"、服务"厚"是 Nest 的分层纪律。下一章进入服务层,看
@Injectable与 DI 容器的完整机制。