The CHI adapter
The chi adapter wires jetwarp’s portable adapter.Adapter API to the go-chi/chi router.
It’s a good default when you want a lightweight, idiomatic net/http stack, or you already use chi in other services and want to keep the same mental model while still getting jetwarp’s portability guarantees.
Install
Add the adapter module to your project:
go get codeberg.org/iaconlabs/jetwarp/adapter/chi@latest
This pulls in:
-
codeberg.org/iaconlabs/jetwarp (core adapter + registry)
-
codeberg.org/iaconlabs/jetwarp/drivers/v1/chi (the driver implementation)
-
codeberg.org/go-chi/chi/v5 (the underlying router)
Create a router
package main
import (
"log"
"net/http"
twchi "codeberg.org/iaconlabs/jetwarp/adapter/chi"
)
func main() {
r := twchi.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)
}
_ = http.ListenAndServe(":8080", r)
}
Route patterns and params
Jetwarp’s canonical pattern syntax is 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))
})
In-segment suffix/prefix parameters
The chi driver advertises support for in-segment literals around a single param, for example:
-
/files/{id}.json -
/prefix-{id}
r.HandleFunc(http.MethodGet, "/files/{id}.json", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("id=" + r.PathValue("id")))
})
Reading parameters portably
jetwarp’s recommended application-level way to read params is (*http.Request).PathValue (Go 1.22+).
Most non-stdlib drivers install parameters into PathValue so your handlers remain portable. If you run into a framework where PathValue isn’t populated for a given adapter/driver, that’s a portability bug and should be treated as such (it belongs in the conformance suite and/or driver regression tests).
MethodAny (“*”) support
Jetwarp supports method-agnostic routes via the canonical method token “*”.
r.HandleFunc("*", "/any", func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("matched regardless of method"))
})
The chi driver implements a “method-specific wins over ANY” rule consistently:
- If you register GET /any and later register * /any (or the reverse), a GET /any request must hit the explicit GET handler.
This rule is enforced by conformance tests and is independent of registration order.
Middleware with chi
chi middleware is already func(http.Handler) http.Handler, which is the same shape as jetwarp’s portable HTTP middleware. In practice, this means you can use most existing chi middleware as-is.
import (
twadapter "codeberg.org/iaconlabs/jetwarp/adapter"
)
r.Use(twadapter.HTTPNamed("request-id", func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Request-Id", "example")
next.ServeHTTP(w, r)
})
}))
The ordering contract is deterministic:
Use → Group → With → per-route → handler
Native chi middleware (mw/chi, v1.2+)
The mw/chi module lets you register a func(http.Handler) http.Handler through jetwarp’s
adapter API via the chi driver’s native registration path.
Chi’s middleware signature is identical to jetwarp’s portable middleware type, so adapter.HTTP()
is usually sufficient. mw/chi exists for API symmetry with the other drivers and for the
specific case where you need access to chi.RouteContext inside the middleware.
import (
chiv5 "github.com/go-chi/chi/v5"
mwchi "codeberg.org/iaconlabs/jetwarp/mw/chi"
)
r.Use(mwchi.Middleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// chi.RouteContext is available here
rctx := chiv5.RouteContext(r.Context())
_ = rctx
next.ServeHTTP(w, r)
})
}, "my-chi-mw"))
On a non-chi adapter, Apply() returns ErrNativeMWUnsupported and the error is
recorded in Err(). The route is still registered and served normally.
Accessing the underlying engine (escape hatch)
Sometimes you need a router feature that’s intentionally outside jetwarp’s portability surface (custom chi routing features, third-party chi tooling, etc.).
The returned router is a concrete *adapter.Router, which exposes an Engine() any escape hatch. This is optional and not part of the portable adapter.Adapter interface, so use it sparingly:
type engineProvider interface {
Engine() any
}
if ep, ok := r.(engineProvider); ok {
eng := ep.Engine()
_ = eng // type-assert to the chi engine type if you really need it
}
If you find yourself relying on the engine often, it’s usually a sign you should either:
-
keep that service “chi-native” (no portability requirement), or
-
write a small helper driver/adapter extension that can be tested in the suite.
Common pitfalls
-
Forgetting to check
Err(): jetwarp accumulates registration errors instead of panicking. Always callErr()after setup. -
Bypassing jetwarp for routing: registering routes directly on the underlying chi engine skips the registry, OpenAPI generation, and conformance guarantees.
-
Depending on non-portable parameter APIs: prefer
r.PathValue("name")in handlers to keep your code adapter-agnostic.
Next steps
- Adapters overview — middleware ordering and scoping rules
- Drivers Overview — portable vs adapter-native middleware