SRouter registers standard and typed routes on one runtime router. Recursive
RouteGroup handles provide path scoping, middleware, and inherited policy
without creating additional HTTP handlers or dispatchers.
r.Route(
router.RouteConfigBase{
Path: "/health",
Methods: []router.HttpMethod{router.MethodGet},
Handler: healthHandler,
},
router.RouteConfig[CreateRequest, CreateResponse]{
Path: "/users",
Methods: []router.HttpMethod{router.MethodPost},
Codec: codec.NewJSONCodec[CreateRequest, CreateResponse](),
Handler: createUser,
},
)Route accepts any number of RouteDefinition values. RouteConfigBase and
every RouteConfig[Req, Resp] instantiation implement that sealed interface.
api := r.Group("/api").Use(apiMiddleware)
v1 := api.Group("/v1").Timeout(3 * time.Second)
users := v1.Group("/users").Auth(router.AuthRequired)
users.Route(
router.RouteConfigBase{
Path: "/:id",
Methods: []router.HttpMethod{router.MethodGet},
Handler: getUser,
},
router.RouteConfig[ListRequest, ListResponse]{
Path: "",
Methods: []router.HttpMethod{router.MethodGet},
Codec: codec.NewJSONCodec[ListRequest, ListResponse](),
SourceType: router.Empty,
Handler: listUsers,
},
)This registers /api/v1/users/:id and the exact /api/v1/users path.
Prefixes must begin with /; non-root prefixes must not end with /.
Groups can be nested to any practical depth. Retain the handle rather than looking it up by a path string:
func registerUsers(group *router.RouteGroup[string, User]) {
group.Route(/* user routes */)
}
api := r.Group("/api")
registerUsers(api.Group("/users"))See Route groups for policy inheritance, middleware order, explicit disabling, and the build/freeze lifecycle.
Wildcards fill a whole path segment:
:namecaptures one non-empty path segment.*namecaptures the remaining path, including its leading/. It must be the final segment.
r.Route(router.RouteConfigBase{
Path: "/users/:id/files/*path",
Methods: []router.HttpMethod{router.MethodGet},
Handler: func(w http.ResponseWriter, req *http.Request) {
id := router.GetParam(req, "id")
path := router.GetParam(req, "path")
_, _ = fmt.Fprintf(w, "%s: %s", id, path)
},
})router.GetParams(req) returns all parameters as scontext.Params, in pattern
order; router.GetParam(req, name) returns one value. Parameters are taken
from the decoded request path, so %20 becomes a space. SRouter also stores
the compiled route template in its request context for built-in metrics and
application middleware or logging.
A catch-all value is not cleaned: /files/*path matches /files/../x with
path=/../x. Clean it before using it as a file path.
A static segment and a wildcard can share a position. At each segment the
router tries a static match first, then a :name parameter, then a *name
catch-all, and backtracks when a choice fails deeper in the path:
get := []router.HttpMethod{router.MethodGet}
r.Route(
router.RouteConfigBase{Path: "/users/new", Methods: get, Handler: newUserForm},
router.RouteConfigBase{Path: "/users/:id", Methods: get, Handler: showUser},
)/users/new reaches newUserForm and /users/42 reaches showUser. The
result never depends on registration order.
When no route matches the method and path, the router responds in this order:
301(forGET) or307(other methods) to the same path with the trailing slash added or removed, when that path matches a route for the request method.- The same redirect to the cleaned path, with repeated slashes,
.and..resolved. Matching stays case-sensitive; there is no case-correcting redirect. - For
OPTIONS,200with anAllowheader when other methods match the path. 405 Method Not Allowedwith anAllowheader when other methods match.404 Not Found.
Redirects keep the query string and only point at routes that exist on the
same host. HEAD is not routed to GET handlers. The full rules are in the
route table specification.
A route may register multiple methods:
r.Route(router.RouteConfigBase{
Path: "/items/:id",
Methods: []router.HttpMethod{router.MethodGet, router.MethodDelete},
Handler: itemHandler,
})Build rejects missing/empty methods, duplicate method/path pairs, invalid
wildcards (such as /user_:name or a repeated name), and two different
parameter or catch-all names at the same position for the same method.
if err := r.Build(); err != nil {
log.Fatal(err)
}
log.Fatal(http.ListenAndServe(":8080", r))Explicit build is recommended so configuration failures stop startup. The first request builds automatically when needed. Once built, the route tree is frozen and steady-state dispatch performs no group traversal or policy resolution.