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

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 (CapNativeScopeMW declared) — middleware is registered; Apply() returns nil.
  • Driver does not support itApply() returns ErrNativeMWUnsupported, which is recorded in Err(). 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.Use has no path-prefix-scoped equivalent. Native gin middleware registered on a Group applies globally. This is a documented v1.2 limitation frozen by adapter/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 Group applies globally. Frozen by adapter/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.Use accepts 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 by adapter/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