Routing Package
github.com/mbvlabs/andurel/pkg/routing provides typed route values and the virtual-host registry. It does not register handlers or choose HTTP methods: route data lives in routes/, and the router that binds it to handlers lives in pkg/anduin. See Routing for the application side and Server for dispatch.
Declaring routes
A route value stores a path, a name, an optional prefix, the named host it belongs to, and whether it should appear in generated TypeScript helpers. Declare them as package variables so handlers and pages share one source of truth.
1package routes
2
3import "github.com/mbvlabs/andurel/pkg/routing"
4
5const UserPrefix = "/users"
6
7var (
8 SessionNew = routing.NewSimpleRoute(
9 "/sign-in",
10 "users.new_user_session",
11 UserPrefix,
12 routing.InertiaRoute(),
13 )
14 PasswordEdit = routing.NewRouteWithToken(
15 "/password/:token/edit",
16 "users.edit_user_password",
17 UserPrefix,
18 routing.InertiaRoute(),
19 )
20)
Every route exposes:
Path()— the application path, with:nameplaceholders, that the router registers;Name()— the route name with the prefix applied (prefix + "." + name);Host()— the bound host,routing.HostPrimaryunlessrouting.Host(...)was passed;IsInertia()— whether the TypeScript generator includes it;URL(...)andFullURL(hosts, ...)— path-only and absolute URL builders.
The prefix is not cosmetic: Name() is prefix + "." + name and Path() joins the prefix and path, tolerating a missing or doubled slash with a logged warning. The scaffold passes a leading-slash prefix (/users) and a name that already carries the section (users.new_user_session), so its Name() reads /users.users.new_user_session; the generated TypeScript key comes from the variable name instead.
Parameter constructors
| Constructor | Placeholder | Go argument |
|---|---|---|
NewSimpleRoute |
none | none |
NewRouteWithUUIDID |
:id |
uuid.UUID |
NewRouteWithSerialID |
:id |
int32 |
NewRouteWithBigSerialID |
:id |
int64 |
NewRouteWithStringID |
:id |
string |
NewRouteWithSlug |
:slug |
string |
NewRouteWithToken |
:token |
string |
NewRouteWithFile |
:file |
string |
NewRouteWithParams[T] |
tagged fields | struct |
Use a params struct when a path has more than one parameter, so call sites cannot swap two same-typed values:
1type ProjectParams struct {
2 Team string `param:"team"`
3 Project string `param:"project"`
4}
5
6var ProjectShow = routing.NewRouteWithParams[ProjectParams](
7 "/:team/projects/:project",
8 "projects.show",
9 "",
10)
11
12url := ProjectShow.URL(ProjectParams{Team: "platform", Project: "orbit"})
13// /platform/projects/orbit
NewRouteWithParams substitutes exported string fields that carry a param tag. A non-struct value returns an empty URL. Prefer the dedicated constructors for numeric and UUID IDs; the generic one does not parse or validate values.
Values are substituted as supplied. Encode user input before passing it into a URL builder.
Hosts and absolute URLs
routing.Hosts is an immutable registry built once at boot. HostPrimary is required; additional names are application constants.
1hosts, err := routing.NewHosts(map[routing.HostName]routing.HostSpec{
2 routing.HostPrimary: {
3 Hostname: cfg.Domain,
4 Protocol: cfg.Protocol,
5 },
6})
HostSpec has a canonical Hostname, optional Aliases, and a Protocol (defaulting to http when empty). HostSpec.BaseURL() returns the origin, and HostSpec.Origins() returns the unique origins for the hostname and aliases.
Pass the same registry to anduin.WithHosts and to FullURL so registration and URL generation agree:
1absolute := routes.PasswordEdit.FullURL(hosts, token)
Hosts.BaseURL returns the empty string for an unregistered name rather than silently falling back to another host, which keeps a misconfigured host visible.
Query parameters and JavaScript expressions
QueryParam appends a key/value pair to a generated URL. It appends the strings as supplied, so encode arbitrary values first.
1next := ProjectShow.URL(
2 ProjectParams{Team: "platform", Project: "orbit"},
3 routing.QueryParam("page", "2"),
4)
routing.JsExpr wraps a JavaScript expression so the route generator interpolates it instead of quoting it. It is for generated TypeScript, not a general escaping function.
Declare canonical paths without query strings or fragments. The path sanitizer trims whitespace, converts backslashes, removes query strings and fragments, collapses double slashes, adds a leading slash, and trims a trailing one. Those warnings mean the declaration is wrong, not that the sanitizer should be relied on.
TypeScript route helpers
routing.InertiaRoute() marks a route for andurel sync routes, which writes resources/js/routes.ts:
1andurel sync routes --json
The generated file is derived; do not edit it. It contains a helper per marked route plus configureRouteHosts, which the frontend calls once with the shared hosts prop:
1export const routes = {
2 sessionNew: routeHelper({
3 host: 'primary',
4 path: () => '/users/sign-in',
5 }),
6 passwordEdit: routeHelper({
7 host: 'primary',
8 path: (token: string) => `/users/password/${token}/edit`,
9 }),
10} as const
routes.passwordEdit.path(token) returns the relative path, and routes.passwordEdit(token) returns the full URL for the helper's host, using the origins registered at boot. Import the helpers instead of writing frontend URL strings by hand.
Registration boundary
The routing package does not register handlers, attach middleware, reverse-match requests, or parse path parameters. Handlers register route.Path() with the intended method:
1func (h AuthHandler) Register(rt *anduin.Router) {
2 rt.Get(routes.SessionNew.Path(), h.SessionNew)
3 rt.Post(routes.SessionCreate.Path(), h.SessionCreate)
4 rt.Get(routes.PasswordEdit.Path(), h.PasswordEdit)
5}
anduin.Router converts :name segments to http.ServeMux {name} wildcards and a trailing * to {path...}. Use anduin.MuxPattern yourself only when registering an andurel-style path outside the router. Exact matching is separate: MuxPattern does not add {$}, so a home route needs the suffix explicitly, for example routes.HomePage.Path() + "{$}".
After changing route data, run andurel sync routes --json for the frontend and andurel inspect routes --json to confirm the manifest. See Code Generation for the other sync commands.