App, Group, Operation. A group is a prefix and what holds under it, an operation
is one typed route declared there, and the verb methods are generic methods —
which Go 1.27 allows on a concrete type and still refuses on an interface. So In
and Out come from the handler and the caller writes no type arguments:
users := app.Group("/v1/users")
users.Post("/", api.Create)
mint := users.Group("/mint").With(middleware.Mint(gate))
mint.Post("/deposit", api.Deposit)
A Group is a CLOSED HANDLE: two unexported fields, and no method that hands back
the app, the parent or the fiber underneath. A subsystem given the group at its
own prefix cannot reach above it, so confinement is a property of the type
instead of a rule something has to police — which is what a host needs in order
to hand out a bounded surface and know what comes back.
That is bought by giving up the router interface, deliberately. Router and
OpTarget are gone, and with them the ability to implement a route table outside
this package: a decorator is no longer a router, a gate is middleware on a group,
and a bound is a path. The five package-level verb functions go the same way —
a registration is a method on the thing it registers into.
Raw carries what is not a typed operation: a stream, an upgrade, a probe, a
foreign subtree. It names its method, and it is the longer spelling because
opting out of the typed model is the exception and should read like one.
Co-authored-by: Hanzo Dev <[email protected]>
3.7 KiB
Migration guide
zip ships drop-in adapters for chi, gin, beego, and stdlib net/http.
Each adapter is documented as a migration tool — they exist so a
service can roll onto zip incrementally. Adapter dispatch costs ~5%
versus native Fiber routing; replace adapted routes with native zip
handlers when feasible.
From gin
gin handlers take *gin.Context; zip handlers take *zip.Ctx. The
shapes are deliberately close.
// gin
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
c.JSON(200, gin.H{"id": c.Param("id")})
})
// zip
app := zip.New(zip.Config{})
app.Get("/users/:id", func(c *zip.Ctx) error {
return c.JSON(200, map[string]string{"id": c.Param("id")})
})
Common mappings:
| gin | zip |
|---|---|
gin.H{...} |
map[string]any{...} or a typed struct |
c.Param("id") |
c.Param("id") |
c.Query("q") |
c.Query("q") |
c.GetHeader("X-Foo") |
c.Header("X-Foo") |
c.BindJSON(&v) |
c.Bind(&v) |
c.AbortWithStatusJSON(400, ...) |
return zip.ErrBadRequest("...") |
gin.Recovery() |
middleware.Recover() |
gin.Logger() |
middleware.Logger(app.Logger()) |
For routes that are too complex to port today, front the gin engine as a legacy adapter on a wildcard route:
gin := buildExistingGinApp()
app.Raw(zip.MethodAll, "/legacy/gin/*", zip.AdaptNetHTTP(gin))
From chi
chi routers implement http.Handler, so zip.AdaptNetHTTP works
unchanged.
chiRouter := chi.NewRouter()
chiRouter.Get("/users", listUsers)
app := zip.New(zip.Config{})
app.Raw(zip.MethodAll, "/legacy/chi/*", zip.AdaptNetHTTP(chiRouter))
Common mappings:
| chi | zip |
|---|---|
chi.NewRouter() |
zip.New(zip.Config{}) |
r.Route("/v1", fn) |
v1 := app.Group("/v1") then register on v1 |
r.Group(fn) |
app.Group("/", h1, h2) |
chi.URLParam(r, "id") |
c.Param("id") |
| middleware (func(http.Handler) http.Handler) | zip.AdaptNetHTTPMiddleware(mw) |
From beego
beego *web.HttpServer exposes its handler chain via BeeApp.Handlers,
which is an http.Handler. Front it on a wildcard route with
zip.AdaptNetHTTP.
import "github.com/beego/beego/v2/server/web"
beeApp := web.NewHttpServer()
// ... existing beego config
app := zip.New(zip.Config{})
app.Raw(zip.MethodAll, "/legacy/iam/*", zip.AdaptNetHTTP(beeApp.Handlers))
Notes:
- beego's auto-routing magic (controllers reflected from struct names) has no zip equivalent — port to native zip handlers when feasible.
- beego's
app.confand beego-specific filters do not carry over; re-implement filters as zip middleware.
From net/http
The zero-config path: any http.Handler works.
existing := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
})
app.Raw(zip.MethodAll, "/legacy/*", zip.AdaptNetHTTP(existing))
For middleware that takes func(http.Handler) http.Handler:
mw := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Custom", "1")
next.ServeHTTP(w, r)
})
}
app.Use(zip.AdaptNetHTTPMiddleware(mw))
Replacing adapters with native handlers
Once a route's traffic warrants it, rewrite from the adapter to native zip:
// before
app.Raw(zip.MethodAll, "/legacy/chi/*", zip.AdaptNetHTTP(chiRouter))
// after (per-route migration)
v1 := app.Group("/v1")
v1.Get("/users", listUsers) // was chiRouter.Get("/users", ...)
v1.Post("/users", createUser)
The ~5% perf delta compounds at high RPS and the adapter chain is a sharp edge for debugging — native handlers see fiber.Ctx directly, adapters bounce through fasthttp ↔ net/http translation.