不能把所有路由写在 main.go 里,因为会导致文件臃肿、作用域混乱、中间件误用;应按功能模块拆分至 internal/routers/ 下,各模块导出 setup 函数并用 group 管理前缀与中间件。

为什么不能把所有路由写在 main.go 里
因为 Fiber 的路由注册是同步、线性堆叠的,app.Get、app.Post 这些调用一旦执行,就直接往内部路由栈追加条目。如果全塞进 main.go,随着接口增多,文件会迅速膨胀到几百行,连找一个 /user/update 都得滚屏十几秒;更严重的是,所有路由共享同一作用域,容易误用未声明的变量、重复引入包、或在不该加中间件的地方全局挂载——比如给后台管理路由加了登录守卫,结果把健康检查 /healthz 也拦住了。
按功能模块建独立路由文件(推荐 internal/routers/ 结构)
不要模仿 Express 的 routes/ 目录平铺法,Fiber 项目应遵循 Go 工程规范,在 internal/routers/ 下建子目录,每个子目录对应一个业务域:
-
internal/routers/user/router.go:只放用户相关路由,如/user、/user/profile -
internal/routers/order/router.go:只管订单,路径前缀统一为/order - 每个文件导出一个
Setup函数,接收*fiber.App和必要依赖(如 DB 实例),不直接调用app.Get
示例 internal/routers/user/router.go:
package user
import "github.com/gofiber/fiber/v3"
func Setup(app *fiber.App, db *sql.DB) {
app.Get("/user/:id", func(c fiber.Ctx) error {
// 业务逻辑
return c.SendString("user")
})
app.Post("/user", func(c fiber.Ctx) error {
// 创建用户
return c.SendStatus(201)
})
}
主启动文件 cmd/app/main.go 只负责按序调用各模块的 Setup:
func main() {
app := fiber.New()
db := initDB()
user.Setup(app, db)
order.Setup(app, db)
log.Fatal(app.Listen(":3000"))
}
用 Group 实现路径前缀与中间件隔离
别手动拼接 /api/v1/user 这种字符串,Fiber 的 Group 是专为这个设计的,它能自动处理前缀、嵌套和中间件作用域:
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
- 每个模块的
Setup函数开头先调用app.Group("/user"),后续所有路由自动带上该前缀 - 中间件只对当前 Group 生效:比如
authMiddleware只挂到userGroup,不影响publicGroup - Group 返回值是
*fiber.Router,可链式调用:app.Group("/admin").Use(auth).Get("/dashboard", handler)
错误示范(硬编码前缀):
app.Get("/api/v1/user/:id", ...) // ❌ 后续改版本号要全局搜替换
正确写法:
v1 := app.Group("/api/v1")
v1.Get("/user/:id", ...) // ✅ 版本升级只需改一行
注意 RebuildTree 不是模块拆分的替代方案
有些文档提到用 app.RebuildTree() 动态注册路由,但这和模块拆分完全不是一回事:
-
RebuildTree是运行时操作,性能开销大,且非线程安全,官方明确说“仅用于开发环境” - 模块拆分是编译期结构组织,影响的是代码可读性、测试隔离性和构建产物大小
- 如果你在
user/router.go里调用了RebuildTree,说明你已经绕过了 Go 的包加载机制,后续单元测试、依赖注入都会出问题
真正需要警惕的是:模块间隐式耦合。比如 user/router.go 直接 import 了 internal/payments/service.go,这违反了功能域边界——应该通过接口抽象,由主函数注入。










