Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 call Err() 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