Skip to content
Routing

Routing

Map URLs to handlers, group routes under shared prefixes and middleware, name them, and generate URLs from names.

Before you start

You have an app from anetos.New(). See Configure your application.

Steps

1. Create the HTTP server

func run() error {
	app, err := anetos.New()
	if err != nil {
		return err
	}
	srv, err := web.NewServer(app) // reads HTTP_*, registers the "http" component
	if err != nil {
		return err
	}
	routes(srv.Router(), &Notes{})

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()
	return app.Run(ctx)
}

(Copied from examples/notes, region main.)

web.NewServer reads the HTTP_* settings (address, timeouts, body limit, CORS…), installs the default middleware and adds the server to the app as the http component. app.Run starts it, and SIGTERM shuts it down gracefully, letting in-flight requests finish.

2. Register routes

func routes(r *web.Router, notes *Notes) {
	r.Get("/", func(c *web.Ctx) error {
		return c.RedirectRoute("notes.index")
	}).Name("home")

	api := r.Group("/notes").As("notes.")
	api.Get("", web.H(notes.List)).Name("index")
	api.Post("", web.H(notes.Store)).Name("store")
	api.Get("/{id}", web.H(notes.Show)).Name("show")
	api.Delete("/{id}", web.H(notes.Delete)).Name("delete")
}

(Region routes.)

  • Get, Post, Put, Patch, Delete and Options register a handler for one method. GET routes also answer HEAD. Handle("", pattern, h) matches every method.
  • Handlers are func(c *web.Ctx) error, or typed handlers wrapped with web.H. See Handlers and requests.

3. Use path parameters

Patterns use Go’s net/http syntax:

PatternMatchesRead with
/posts/{id}/posts/42c.Param("id") or a path:"id" field
/files/{path...}/files/a/b.txt (the rest of the path)c.Param("path") → a/b.txt
/about/only /about/; /about is redirected to it

Note: Unlike raw http.ServeMux, a pattern ending in / matches only that path, not everything below it. Use a {name...} wildcard for a subtree.

4. Group routes

r.Group(prefix, middleware...) returns a router whose routes share a path prefix and middleware. As("prefix.") adds a prefix to their names. Groups nest. Inside a group, "" and "/" both mean the group’s own path (/posts, without a trailing slash).

// illustrative
admin := r.Group("/admin", requireAdmin).As("admin.")
admin.Get("/users", h.Users).Name("users") // GET /admin/users, name "admin.users"

5. Name routes and generate URLs

Give routes names and build URLs from them, so paths live in one place:

// illustrative
url, err := c.URL("notes.show", note.ID) // "/notes/7"
return c.RedirectRoute("notes.index")    // 303 See Other
return web.RedirectRoute("notes.show", note.ID), nil // from a typed handler

Arguments fill the wildcards in order and are path-escaped; a url.Values after them becomes the query string (c.URL("notes.index", url.Values{"page": {"2"}}) is "/notes?page=2"). A wrong name or argument count is an error. r.MustURL panics instead, which is useful for fixed links at startup.

6. Add middleware

Middleware is plain func(http.Handler) http.Handler, so any net/http middleware works.

CallApplies to
r.UseGlobal(mw...)Every request, including 404/405, before routing. Call it before the server starts
r.Use(mw...)Routes registered on r (and groups created from it) afterwards. It panics if r or any of its groups already has routes, so middleware can’t silently miss routes
r.Group(prefix, mw...)Routes in the group
r.With(mw...).Get(...)A single route

web.NewServer already installs these global middleware, outermost first: Recover, RequestIDs, RealIP, AccessLog, SecureHeaders, CORS (when configured), the request’s locale (with translations), BodyLimit and Timeout. See the configuration reference. To limit how often clients call a group of routes, add ratelimit.Middleware to it: see Rate limiting.

How it works

The router registers your patterns on a standard http.ServeMux, adding the method and making trailing-slash patterns exact. A catch-all route handles everything else:

  • A path that exists for other methods gets 405 Method Not Allowed with an Allow header listing them (including custom methods such as PURGE), and OPTIONS gets 204 with Allow.
  • Anything else gets 404, as JSON problem details or an HTML page (see Errors).

Coming from Laravel? Route::get(...)->name('x'), Route::prefix() groups and route('x', $id) map to r.Get(...).Name("x"), r.Group() and c.URL("x", id).

Testing it

The router is an http.Handler, so httptest works directly:

// illustrative
r := web.NewRouter()
routes(r, &Notes{})
rec := httptest.NewRecorder()
r.ServeHTTP(rec, httptest.NewRequest("GET", "/notes/1", nil))

Common problems

SymptomCauseFix
Panic: Use called after routes were addedr.Use after r.Get(...)Call Use first, or use r.With(mw).Get(...)
Panic: pattern "posts" must start with /Missing slashWrite /posts
Panic: … conflicts with … (from net/http)Two routes match the same requestsRemove the duplicate or make one more specific
/about redirects to /about/The route is /about/Register /about instead
Panic: duplicate route nameSame name used twiceNames are global; use group prefixes (As)

Next steps