Mounting
Mount sends every request at or below a pattern, on any method, to another handler, with the pattern’s segments removed from the path.
r.Mount("/api/v1", v1) // v1 sees /users/7 for /api/v1/users/7r.Mount("/orgs/{org}", orgs) // r.PathValue("org") still works insider.Mount("/static", http.FileServerFS(os.DirFS("public"))) // any http.Handler worksThe mounted handler doesn’t need to know where it lives: a sub-router registers its routes from /.
The request is copied before the path is rewritten, as http.StripPrefix does, so middleware around the mount still sees the original path.
Splitting an app into sub-routers
Section titled “Splitting an app into sub-routers”// Command mount splits an API into sub-routers, each mounted under a path of the main router.// A sub-router sees paths relative to its mount point, and params in the mount path stay readable.package main
import ( "fmt" "log" "net/http" "time"
"github.com/vardius/gorouter/v5")
func newRouter() *gorouter.Router { r := gorouter.New() r.GET("/", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, "see /api/v1 and /orgs/{org}") }) r.Mount("/api/v1", apiV1()) r.Mount("/orgs/{org}", orgs())
return r}
// apiV1 knows nothing about where it is mounted: its routes start at /.func apiV1() *gorouter.Router { r := gorouter.New() r.GET("/status", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, "v1 ok") }) r.GET("/users/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "v1 user %s\n", r.PathValue("id")) })
return r}
// orgs reads org from the mount path and repo from its own route.func orgs() *gorouter.Router { r := gorouter.New() r.GET("/repos/{repo}", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s/%s (path %s)\n", r.PathValue("org"), r.PathValue("repo"), r.URL.Path) })
return r}
func main() { srv := &http.Server{Addr: ":8080", Handler: newRouter(), ReadHeaderTimeout: 5 * time.Second} log.Fatal(srv.ListenAndServe())}| Request | Handled by | Path it sees |
|---|---|---|
GET /api/v1/status |
apiV1 |
/status |
GET /api/v1/users/7 |
apiV1 |
/users/7 |
GET /orgs/vardius/repos/gorouter |
orgs, with org = vardius |
/repos/gorouter |
- A mount owns its path and everything below it, on every method, custom ones included. Registering a route below a mount, or mounting over existing routes, panics.
- A request below the mount that the sub-router doesn’t match gets the sub-router’s 404 or 405, not the parent’s.
- Middleware on the parent at the mount path or above wraps the whole mounted handler.
- Middleware for paths below the mount belongs in the sub-router:
r.Use("/api/v1/admin", mw)next tor.Mount("/api/v1", v1)panics, because it could never run. Routes()lists a mount once, with an empty method.