Skip to content
第 3 章 后端 ⏱ 14 分钟阅读

第 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 前否则被吞掉

动手练习 ​

  1. 搜索接口:写 GET /api/articles,支持 q/page/limit/sort 参数
  2. 详情接口:写 GET /api/articles/:id,在 :id 前放 @Get('hot')
  3. 更新接口:写 PUT /api/articles/:id,Body 带 DTO

下一章:第 4 章:提供者 →

本站基于 VitePress 构建 · 由 StackHub 团队维护