The STDLIB adapter
The stdlib adapter wires jetwarp’s portable adapter.Adapter API to the Go standard library router,
net/http’s ServeMux.
If you want the smallest dependency footprint and the most “plain net/http” experience, this is the best place to start.
Install
Add the adapter module to your project:
go get codeberg.org/iaconlabs/jetwarp/adapter/stdlib@latest
This pulls in:
codeberg.org/iaconlabs/jetwarp(core adapter + registry)codeberg.org/iaconlabs/jetwarp/drivers/v1/stdlib(the stdlib driver)
No third-party router dependencies are required.
Create a router
package main
import (
"log"
"net/http"
twstdlib "codeberg.org/iaconlabs/jetwarp/adapter/stdlib"
)
func main() {
r := twstdlib.New()
r.HandleFunc(http.MethodGet, "/healthz", func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("ok"))
})
// Always check accumulated registration errors before serving traffic.
if err := r.Err(); err != nil {
log.Fatal(err)
}
log.Fatal(http.ListenAndServe(":8080", r))
}
Route patterns and params
The stdlib adapter is the “native home” of jetwarp’s canonical pattern model.
Parameters ({name}) and PathValue
Jetwarp patterns use ServeMux-style {name} parameters:
r.HandleFunc(http.MethodGet, "/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
_, _ = w.Write([]byte("id=" + id))
})
Because this is standard library routing, PathValue works without any driver bridging.
MethodAny ("*") support
Jetwarp supports method-agnostic routes via the canonical method token "*".
On stdlib, this maps naturally to how Go 1.22+ ServeMux patterns work:
- method-specific registration uses a mux pattern like:
"GET /path" - MethodAny registration uses the raw path:
"/path"
r.HandleFunc("*", "/any", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("ANY:" + r.Method))
})
Method-specific beats ANY for the same path. If you register both:
GET /any* /any
a GET /any request must hit the GET handler (independent of registration order).
Scoping (Group) on stdlib
The stdlib driver supports scoping by returning derived drivers that share the same underlying ServeMux
and apply a prefix internally.
From application code, you’ll normally use Group:
v1 := r.Group("/api").Group("/v1")
v1.HandleFunc(http.MethodGet, "/students", listStudents)
The router will join prefixes using jetwarp’s path-join rules (no accidental double slashes).
Important gotchas
1) No in-segment suffix/prefix params
The stdlib driver does not claim support for in-segment suffix/prefix patterns (CapParamSuffix).
That means patterns like these are not portable on stdlib and should be rejected during registration:
/files/{id}.json/prefix-{id}
If you need this feature, use an adapter whose driver advertises param-suffix support.
Portable alternative: use a plain segment param and validate/parse in the handler:
- ✅
/files/{id}then parse/validate - ❌
/files/{id}.json(not supported on stdlib)
2) Duplicate registrations can trigger ServeMux panics (but jetwarp turns them into errors)
http.ServeMux may panic when registering invalid or conflicting patterns (including duplicates).
The stdlib driver protects you: it converts those panics into returned errors and keeps the driver usable.
Still, it’s a good reason to treat Err() as a required setup check.
3) Trailing slashes are normalized away
Jetwarp normalizes patterns by trimming trailing slashes for non-root paths.
This is intentional (it avoids ambiguous “/x vs /x/” behavior), but it also means you should not rely on
ServeMux’s legacy “subtree” semantics that depend on patterns ending with /.
If you need a file-server-style subtree mount, consider:
- mounting it on a separate parent
http.ServeMux, or - using a router/driver that supports your desired prefix matching model explicitly.
Accessing the underlying engine (escape hatch)
If you really need direct access to the underlying mux for debugging or integration, the stdlib driver’s engine is
a *http.ServeMux.
Be careful: registering routes directly on the mux bypasses jetwarp’s registry and tooling (including OpenAPI).
type engineProvider interface {
Engine() any
}
if ep, ok := r.(engineProvider); ok {
if mux, ok := ep.Engine().(*http.ServeMux); ok {
_ = mux
}
}