首页 / NestJS 入门教程 / 路由深入

NestJS 入门教程

路由深入

本教程共 47 篇 · 第 11 篇 · 更新于 2026-08-09 · 约 9 分钟阅读

NestJS路由版本控制全局前缀通配符RESTful子域路由

本节目标:掌握 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/123abcd/abc/def 都能匹配。

Note

Express v5 要求用命名通配符,比如 abcd/*splat。NestJS 提供了兼容层,* 写法也能用,但推荐用命名写法以保持一致性。

可选参数

路由参数可以标记为可选——加个 ?

@Get(':id?')
findOne(@Param('id') id?: string) {
  if (id) {
    return `用户 ID: ${id}`;
  }
  return '返回所有用户';
}

GET /usersGET /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() {}
}
Note

URI 版本控制是最直观的方式。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 方法表示操作
  • 装饰器可以放在控制器级别或方法级别