Native middleware (v1.2+)
Stability: Experimental. The
mw/*API may evolve in minor releases with notice in CHANGELOG.
Jetwarp v1.2 introduced native middleware support for gin, echo, chi, and fiber.
Native middleware lets you use framework-specific middleware types (gin.HandlerFunc,
echo.MiddlewareFunc, fiber.Handler) directly through the jetwarp adapter API,
inside the same registration flow as portable middleware.
When to use native middleware
For maximum portability, prefer adapter.HTTP(...) — it works identically on every driver:
r.Use(adapter.HTTP(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// runs on every driver
next.ServeHTTP(w, r)
})
}))
Reach for native middleware when you need framework-specific access that portable
middleware cannot express — for example gin.Context.Abort(), echo.Context.Logger(),
or fiber.Ctx.Locals().
How it works
Each mw/<driver> module provides a wrapper that implements adapter.MW.
When Apply() is called, the wrapper checks via type assertion whether the underlying
driver exposes the optional Use* interface:
- Driver supports it (
CapNativeScopeMWdeclared) — middleware is registered;Apply()returnsnil. - Driver does not support it —
Apply()returnsErrNativeMWUnsupported, which is recorded inErr(). The route is still registered and served.
The coupling is fully contained in each mw/* package — the core drv.Drv interface does not change.
Gin — mw/gin
import (
"github.com/gin-gonic/gin"
mwgin "codeberg.org/iaconlabs/jetwarp/mw/gin"
)
r.Use(mwgin.Handler(func(c *gin.Context) {
c.Set("request-id", generateID())
c.Next()
}, "request-id"))
The optional name argument is used for diagnostics and String() output.
Scope note:
gin.Engine.Usehas no path-prefix-scoped equivalent. Native gin middleware registered on aGroupapplies globally. This is a documented v1.2 limitation frozen byadapter/gin/native_mw_scope_regression_test.go.
Echo — mw/echo
import (
echov5 "github.com/labstack/echo/v5"
mwecho "codeberg.org/iaconlabs/jetwarp/mw/echo"
)
r.Use(mwecho.Middleware(func(next echov5.HandlerFunc) echov5.HandlerFunc {
return func(c echov5.Context) error {
return next(c)
}
}, "my-echo-mw"))
Scope note: same global-apply limitation as gin. Native echo middleware on a
Groupapplies globally. Frozen byadapter/echo/native_mw_scope_regression_test.go.
Chi — mw/chi
Chi’s middleware signature (func(http.Handler) http.Handler) matches jetwarp’s portable type.
mw/chi exists for API symmetry and for cases where you need 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) {
rctx := chiv5.RouteContext(r.Context())
_ = rctx
next.ServeHTTP(w, r)
})
}, "my-chi-mw"))
For middleware that does not need chi.RouteContext, adapter.HTTP(...) is equivalent and more portable.
Scope note: same global-apply limitation. Frozen by
adapter/chi/native_mw_scope_regression_test.go.
Fiber — mw/fiber
import (
fiberv3 "github.com/gofiber/fiber/v3"
mwfiber "codeberg.org/iaconlabs/jetwarp/mw/fiber"
)
r.Use(mwfiber.Handler(func(c fiberv3.Ctx) error {
c.Locals("request-id", generateID())
return c.Next()
}, "request-id"))
Fiber is different:
fiber.App.Useaccepts a path prefix, so native fiber middleware registered on a scoped router (e.g.r.Group("/api")) is correctly scoped to/api. This is the only driver where native group-level middleware is truly prefix-scoped. Frozen byadapter/fiber/native_mw_scope_regression_test.go.
Mixing portable and native
Portable and native middleware can be combined in any Use, Group, With, or Handle call:
r.Use(
adapter.HTTP(portableLogger), // portable — runs on every driver
mwgin.Handler(ginRecovery), // native gin — ErrNativeMWUnsupported on non-gin drivers
)
Portable ordering is fully contractual: Use → Group → With → per-route → handler.
Native ordering is best-effort and driver-specific.
Portable ↔ native interleaving is best-effort and not part of the cross-driver contract.
Checking for errors
Always call r.Err() after setup. ErrNativeMWUnsupported is a warning — the route
is still registered:
r.Use(mwgin.Handler(h)) // on a chi adapter
if err := r.Err(); err != nil {
// wraps ErrNativeMWUnsupported
log.Printf("setup: %v", err)
}
Full runnable example
See examples/native-middleware
in the repository — all four drivers, portable + native middleware side by side,
with an integration smoke test (example_test.go).
See also
- Groups, With and Middlewares
- Adapters overview
- Drivers overview
- Tests & suite — regression test strategy