第 3 章:路由与参数
学习目标
- 掌握 @Param / @Query / @Body 取参
- 理解动态路由、通配符
- 学会子路由(Controller 嵌套)
- 避开 4 个参数和路由的常见坑
一、路由装饰器全表
| 来源 | 装饰器 | 示例 |
|---|---|---|
| URL 路径 | @Param('id') | /users/:id |
| 查询字符串 | @Query('q') | /search?q=nest |
| 请求体 | @Body() | POST JSON |
| 头 | @Headers('token') | Header: token=xxx |
| Cookie | @Cookies('sid') | Cookie: sid=xxx |
| IP | @Ip() | - |
二、@Param 路径参数
typescript
@Get(':id')
findOne(@Param('id') id: string) {
return this.svc.findOne(+id); // ⚠️ id 是 string,转数字要 +
}
@Get(':category/:id')
findInCategory(
@Param('category') cat: string,
@Param('id') id: string,
) { return { cat, id }; }测试:GET /users/42 → { id: '42' }。
bash
curl http://localhost:3000/users/42
# {"id":"42"}⚠️ 坑 1:
id默认是字符串,数字比较要手动+id或Number(id),否则"42" > 100为 false。
三、@Query 查询参数
typescript
@Get()
search(
@Query('q') q: string,
@Query('page') page = '1',
@Query('limit') limit = '20',
) {
return { q, page: +page, limit: +limit };
}测试:GET /search?q=nest&page=2&limit=5 → { q: 'nest', page: 2, limit: 5 }。
拿整个对象:
typescript
@Get()
list(@Query() query: Record<string, string>) {
return query; // { q: 'nest', page: '2', ... }
}四、@Body 请求体
typescript
import { Body } from '@nestjs/common';
@Post()
create(@Body() dto: CreateUserDto) {
return this.svc.create(dto);
}
interface CreateUserDto {
name: string;
age: number;
}测试:
bash
curl -X POST http://localhost:3000/users \
-H 'Content-Type: application/json' \
-d '{"name":"Tom","age":18}'
# {"name":"Tom","age":18}⚠️ 坑 2:不写
-H 'Content-Type: application/json'→ NestJS 收不到 body,dto是{}。 ⚠️ 坑 3:@Body() body: any直接用any失去类型保护 → 用 DTO + class-validator(见第 8 章)。
五、组合参数
typescript
@Put(':id')
update(
@Param('id') id: string,
@Body() dto: UpdateUserDto,
@Headers('if-match') version?: string,
) {
return this.svc.update(+id, dto, version);
}六、动态路由(参数化路径)
typescript
@Get('files/:filename(*)') // * 通配,匹配含 / 的路径
getFile(@Param('filename') name: string) {
// /files/a/b/c.txt → name = 'a/b/c.txt'
return this.svc.getFile(name);
}* 通配符可匹配斜杠,+ 表示 1 个或多个字符,? 表示可选。
七、子路由(Controller 嵌套)
适合版本号、模块前缀。
typescript
// admin/articles.controller.ts
@Controller('admin/articles')
export class AdminArticlesController {
@Get()
list() { /* GET /admin/articles */ }
@Post()
create() { /* POST /admin/articles */ }
}或者用 RouterModule:
typescript
// app.module.ts
import { RouterModule, Routes } from '@nestjs/core';
const routes: Routes = [
{ path: 'admin', module: AdminModule },
{ path: 'api/v1', module: ApiV1Module },
];
@Module({
imports: [AdminModule, ApiV1Module, RouterModule.register(routes)],
})
export class AppModule {}八、路由顺序(声明顺序敏感)
typescript
@Controller('users')
export class UsersController {
@Get('me') // ⚠️ 必须放在 :id 之前!
me() { return { id: 0, name: 'me' }; }
@Get(':id')
findOne(@Param('id') id: string) { return { id }; }
}⚠️ 坑 4:声明顺序错了 →
GET /users/me会匹配到:id='me'→ 返回{ id: 'me' }。
九、Header、Cookie、IP
typescript
@Get('whoami')
whoami(
@Headers('authorization') auth: string,
@Cookies('sid') sid: string,
@Ip() ip: string,
) {
return { auth, sid, ip };
}要用 @Cookies,需要在 main.ts 装 cookie-parser:
typescript
import cookieParser from 'cookie-parser';
const app = await NestFactory.create(AppModule);
app.use(cookieParser());十、本章小结
| 参数 | 装饰器 | 典型坑 |
|---|---|---|
| 路径 | @Param('id') | 默认是 string,数字要 +id |
| 查询 | @Query('q') | 也是 string |
| 体 | @Body() | 忘加 Content-Type 收不到 |
| 头 | @Headers('x') | 大小写不敏感 |
| 路由顺序 | @Get('me') 必须在 :id 前 | 否则被吞掉 |
动手练习
- 搜索接口:写
GET /api/articles,支持q/page/limit/sort参数 - 详情接口:写
GET /api/articles/:id,在:id前放@Get('hot') - 更新接口:写
PUT /api/articles/:id,Body 带 DTO
下一章:第 4 章:提供者 →