Skip to content

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/7
r.Mount("/orgs/{org}", orgs) // r.PathValue("org") still works inside
r.Mount("/static", http.FileServerFS(os.DirFS("public"))) // any http.Handler works

The 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.

examples/mount/main.go
// 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 to r.Mount("/api/v1", v1) panics, because it could never run.
  • Routes() lists a mount once, with an empty method.