路由深入
本教程共 47 篇 · 第 11 篇 · 更新于 2026-08-09 · 约 9 分钟阅读
本节目标:掌握 NestJS 路由的高级用法——匹配规则、版本控制、全局前缀、子域路由、RESTful 设计规范。
路由匹配顺序
NestJS 按照方法在控制器中的定义顺序匹配路由。这个顺序很重要——静态路由要写在动态路由前面。
@Controller('users')
export class UsersController {
@Get('profile') // 静态路由,先匹配
getProfile() {}
@Get(':id') // 动态路由,后匹配
findOne() {}
}
如果顺序反了:
@Controller('users')
export class UsersController {
@Get(':id') // 先匹配——/users/profile 会被这里截走
findOne() {} // id = 'profile'
@Get('profile') // 永远不会被匹配到
getProfile() {}
}
访问 GET /users/profile 时,:id 会匹配到 'profile',getProfile() 永远不会被调用。
Warning很多人踩过这个坑。记住:静态路由永远写在动态路由前面。
通配符路由
用 * 做通配符,匹配任意字符:
@Get('abcd/*')
findAll() {
return '匹配 abcd/ 后面的任意路径';
}
abcd/、abcd/123、abcd/abc/def 都能匹配。
NoteExpress v5 要求用命名通配符,比如
abcd/*splat。NestJS 提供了兼容层,*写法也能用,但推荐用命名写法以保持一致性。
可选参数
路由参数可以标记为可选——加个 ?:
@Get(':id?')
findOne(@Param('id') id?: string) {
if (id) {
return `用户 ID: ${id}`;
}
return '返回所有用户';
}
GET /users 和 GET /users/42 都能匹配这个方法。
全局路由前缀
在 main.ts 里设置全局前缀,所有路由都会加上这个前缀:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
await app.listen(3000);
}
bootstrap();
效果:
/users→/api/v1/users/users/1→/api/v1/users/1/products→/api/v1/products
排除某些路由
有些路由不想加前缀(比如健康检查),用 exclude 排除:
app.setGlobalPrefix('api/v1', {
exclude: ['health', 'metrics', '(.*)'],
});
也可以用路由方法级别的排除:
import { RequestMethod } from '@nestjs/common';
app.setGlobalPrefix('api', {
exclude: [
{ path: 'health', method: RequestMethod.GET },
{ path: 'metrics', method: RequestMethod.GET },
],
});
Tip健康检查接口
/health通常会被负载均衡器或监控系统调用,不适合放在/api下面。记得排除。
API 版本控制
同一个接口可能有多个版本。NestJS 内置了版本控制:
// 先在 main.ts 启用版本控制
app.enableVersioning({
type: VersioningType.URI,
});
然后在控制器上指定版本:
@Controller({ path: 'users', version: '1' })
export class UsersV1Controller {
@Get()
findAll() {
return 'V1 版本的用户列表';
}
}
@Controller({ path: 'users', version: '2' })
export class UsersV2Controller {
@Get()
findAll() {
return 'V2 版本的用户列表(带分页)';
}
}
访问方式:
GET /v1/users→ V1 控制器GET /v2/users→ V2 控制器
也支持在方法级别指定版本:
@Controller('users')
export class UsersController {
@Get()
@Version('1')
findAllV1() {}
@Get()
@Version('2')
findAllV2() {}
}
NoteURI 版本控制是最直观的方式。NestJS 还支持 Header 版本控制(通过请求头
Accept传递版本号),适合对 URL 整洁度有要求的场景。
子域路由
根据请求的域名路由到不同的控制器:
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get()
index() {
return '管理后台';
}
}
@Controller({ host: 'api.example.com' })
export class ApiController {
@Get()
index() {
return 'API 接口';
}
}
还支持动态子域:
@Controller({ host: ':tenant.example.com' })
export class TenantController {
@Get()
index(@HostParam('tenant') tenant: string) {
return `租户: ${tenant}`;
}
}
abc.example.com 访问时,tenant 的值就是 'abc'。
Tip子域路由适合多租户 SaaS 应用。不同租户通过不同子域访问,后端用同一套代码处理。
资源嵌套
RESTful API 中,资源之间经常有从属关系。比如用户下面有多篇文章:
@Controller('users/:userId/posts')
export class UserPostsController {
@Get()
findAll(@Param('userId') userId: string) {
return `用户 ${userId} 的所有文章`;
}
@Get(':id')
findOne(
@Param('userId') userId: string,
@Param('id') id: string,
) {
return `用户 ${userId} 的文章 ${id}`;
}
@Post()
create(
@Param('userId') userId: string,
@Body() createPostDto: CreatePostDto,
) {
return `为用户 ${userId} 创建文章`;
}
}
路由:
GET /users/1/posts→ 获取用户 1 的所有文章GET /users/1/posts/5→ 获取用户 1 的第 5 篇文章POST /users/1/posts→ 为用户 1 创建文章
Note资源嵌套不要超过两层。
/users/1/posts/5/comments/3这种三层嵌套太深了,建议把 comments 单独作为一级资源。
RESTful 路由设计规范
一套标准的 RESTful 路由:
| 操作 | HTTP 方法 | 路由 | 说明 |
|---|---|---|---|
| 获取列表 | GET | /users | 返回所有用户 |
| 获取单个 | GET | /users/:id | 返回指定用户 |
| 创建 | POST | /users | 创建新用户 |
| 完整更新 | PUT | /users/:id | 替换整个用户 |
| 部分更新 | PATCH | /users/:id | 更新部分字段 |
| 删除 | DELETE | /users/:id | 删除用户 |
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Put(':id')
update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(+id, updateUserDto);
}
@Patch(':id')
partialUpdate(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.partialUpdate(+id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}
几个设计原则:
- URL 用名词复数(
/users不是/getUsers) - 用 HTTP 方法表示操作(GET 查、POST 增、PUT 改、DELETE 删)
- 不要在 URL 里用动词(
/users/delete/1是反面教材)
路由级别的装饰器
NestJS 支持在控制器和方法上叠加各种装饰器:
@Controller('users')
@UseGuards(AuthGuard) // 控制器级别:所有路由都要认证
@UseInterceptors(LogInterceptor) // 控制器级别:所有路由都记录日志
export class UsersController {
@Get()
findAll() {}
@Get('public')
@Public() // 方法级别:这个路由不需要认证
getPublicData() {}
}
装饰器的作用范围:
- 放在控制器类上 → 影响该控制器的所有路由
- 放在方法上 → 只影响该路由
Tip控制器级别的装饰器相当于”默认设置”,方法级别的装饰器可以覆盖或补充。
路由调试
开发时想看看应用注册了哪些路由,可以开启详细日志:
const app = await NestFactory.create(AppModule, {
logger: ['log', 'error', 'warn', 'debug', 'verbose'],
});
启动时 NestJS 会在控制台打印所有注册的路由映射。
小结
路由是 NestJS 最基础也最常用的功能。
核心要点:
- 静态路由写在动态路由前面
- 全局前缀用
setGlobalPrefix,可以排除特定路由 - 版本控制用
enableVersioning+version选项 - 子域路由适合多租户场景
- RESTful 设计:名词复数 + HTTP 方法表示操作
- 装饰器可以放在控制器级别或方法级别