Skip to content
latest
Framework Packages / latest

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 :name placeholders, that the router registers;
  • Name() — the route name with the prefix applied (prefix + "." + name);
  • Host() — the bound host, routing.HostPrimary unless routing.Host(...) was passed;
  • IsInertia() — whether the TypeScript generator includes it;
  • URL(...) and FullURL(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.