v1.1.0 v1.0.0 · stable roots GA · x/* families carry explicit maturity labels View release posture →

Framework Comparison

Go HTTP Toolkits

Gin, Echo, Fiber, Chi, and Plumego — what each one trades away and what it gets in return. This page is a decision aid, not a benchmark race.

The 30-second verdict.

All five toolkits are built for Go HTTP services. The differences are philosophy and scope, not raw performance at typical request volumes. Read the verdict row first, then follow the deep-dive that matches your team's actual constraint.

Star counts are approximate GitHub figures captured in May 2026 and will grow over time. They are a rough ecosystem-size signal, not a quality ranking.

Gin

~80k ★

Martini-like web framework, radix tree router

Best-in-class speed for teams happy with a custom context type. Large ecosystem. The go-to default for many Go teams.

func(c *gin.Context)

Echo

~30k ★

High performance, minimalist web framework

Clean API, good middleware ecosystem, error return in handler is ergonomic. Solid choice when the custom context trade-off is acceptable.

func(c echo.Context) error
Fiber uses Fasthttp, not net/http. It is incompatible with the Go standard library HTTP ecosystem.

Fiber

~34k ★

Express-inspired, Fasthttp-based web framework

Highest raw throughput by benchmark. The tradeoff is total incompatibility with net/http — no stdlib middleware, no net/http handler, no standard library ecosystem.

func(c *fiber.Ctx) error

Chi

~18k ★

Lightweight, idiomatic router for Go HTTP services

The minimalist's choice. Pure stdlib, zero opinions on response format, service shape, or module stability. If you want to build everything yourself, Chi is the right starting point.

func(w http.ResponseWriter, r *http.Request)

Plumego

v1.0.0 ★

Explicit HTTP toolkit — stdlib-only, machine-readable specs, v1.0.0

For teams where route ownership, review clarity, and a shared service shape matter more than framework convenience. The only choice if your team uses AI coding tools and wants machine-readable ownership signals.

func(w http.ResponseWriter, r *http.Request)

Feature matrix.

Read across a row to compare one property across all five toolkits. The matrix does not capture ecosystem size, community, or raw performance — those depend on your benchmark. It captures structural trade-offs that don't change with load.

Property Gin Echo Fiber Chi Plumego
stdlib handler signature func(w http.ResponseWriter, r *http.Request)
net/http middleware reuse No adapter needed for existing stdlib middleware
Zero external dependencies Stable core uses only Go standard library
Built-in response contract Typed JSON envelope for success and error responses
Canonical service shape Reference app for bootstrap, routing, and handler layout
Explicit module stability tiers stable / beta / experimental labels with adoption guidance
AI / agent-ready control plane specs/task-routing.yaml classifies work before code is opened; specs/change-recipes/ defines ordered steps for 15 change types; module.yaml scopes each package; internal/checks/ enforces boundaries in CI

Fiber's net/http incompatibility is not a missing feature — it's a deliberate architectural choice that enables higher throughput. See the Fiber deep-dive for when this trade is worth making.

The handler signature is the most consequential choice.

Custom context types (Gin, Echo, Fiber) break compatibility with the existing net/http middleware ecosystem. If you adopt one of these frameworks, every stdlib middleware needs an adapter. With Chi and Plumego, the entire net/http world works out of the box.

Gin custom context
handler.go
// gin.Context — net/http middleware needs adapter
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"id": id})
})

// to reuse net/http middleware:
r.Use(gin.WrapH(myStdlibMiddleware))
Echo custom context
handler.go
// echo.Context + error return
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    return c.JSON(http.StatusOK,
        map[string]any{"id": id})
})
Fiber fasthttp — not net/http
handler.go
// fasthttp — incompatible with net/http entirely
// zero net/http middleware can be reused
app.Get("/users/:id", func(c *fiber.Ctx) error {
    id := c.Params("id")
    return c.JSON(fiber.Map{"id": id})
})
Chi stdlib handler
handler.go
// pure stdlib — but no response contract
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    // build your own JSON response every time
    json.NewEncoder(w).Encode(map[string]any{"id": id})
})
Plumego stdlib handler + contract
internal/handler/users.go
// stdlib signature + typed response contract
r.Get("/users/:id", func(w http.ResponseWriter, r *http.Request) {
    id := router.Param(r, "id")
    contract.WriteResponse(w, r, http.StatusOK, user, nil)
    // → {"data": {...}, "meta": null}
})

// all net/http middleware works without adapters
r.Use(middleware.RequestID)
r.Use(myOtelMiddleware)  // any net/http middleware

Response contracts: consistent envelope vs. build your own.

Gin, Echo, Fiber, and Chi all leave the JSON response format to you. This means each team, each handler, or each project may produce subtly different shapes. Plumego's contract module enforces one envelope for both success and error responses.

Gin / Echo / Fiber / Chi — roll your own

handler.go
// success — each team decides the shape
c.JSON(200, gin.H{
    "data":    user,
    "success": true,   // some teams add this
    "code":    0,          // some teams add this
})

// error — even more inconsistent
c.JSON(400, gin.H{
    "error":   "name required",
    "message": "...",       // duplicate? missing?
    "code":    1001,
})

Plumego — one contract, all handlers

internal/handler/users.go
// success — always {"data": ..., "meta": ...}
contract.WriteResponse(w, r, 200, user, nil)

// error — always {"error": {"type": ..., "message": ...}}
contract.WriteError(w, r,
    contract.NewErrorBuilder().
        Type(contract.TypeRequired).
        Field("name").
        Message("name is required").
        Build())

// same envelope from every handler, every service

When to choose each framework.

The table below is not intended to declare a winner. Each row answers two questions: what does this framework excel at, and what should make you pause. The honest answer to "when NOT to choose Plumego" is at the bottom.

Framework Choose when Pause when
Gin
  • Your team wants the fastest possible time-to-working-API
  • You need a large plugin ecosystem for auth, rate limiting, etc.
  • The team is comfortable with gin.Context and its trade-offs
  • Existing Gin code makes the switching cost too high
  • You need plain func(w, r) handlers so existing stdlib middleware works without adapters
  • You want route ownership and middleware order visible in code review without reading framework internals
  • You want a structured, consistent error and response envelope across all handlers
Echo
  • You prefer returning errors from handlers rather than writing to w directly
  • You want a clean, well-documented framework with good middleware support
  • The team appreciates the grouped routing and binder/validator conveniences
  • You need plain net/http compatibility — echo.Context breaks middleware reuse
  • You need a consistent JSON envelope; Echo leaves response formatting to you
  • You want one canonical service shape across multiple teams
Fiber ⚠ fasthttp
  • Raw HTTP throughput is the primary constraint and you have benchmarks proving it matters
  • You are building a greenfield service with no existing net/http middleware to integrate
  • The team has Fasthttp experience and understands the net/http incompatibility trade-off
  • You have any net/http middleware, handlers, or tooling — none of it will work
  • You use net/http-based observability, tracing, or auth libraries
  • Your team is Go-stdlib-first and expects standard library compatibility
Chi
  • You only need routing — you are happy assembling your own response envelope, middleware, and security layer
  • You want pure stdlib compatibility with no framework opinions on service structure
  • The team prefers maximum flexibility and is willing to build and maintain the surrounding layer
  • You want a consistent response envelope across handlers without implementing it yourself
  • You need a canonical service shape to onboard new team members quickly
  • You want explicit maturity signals per module rather than all-or-nothing routing
Plumego
  • Route registration, middleware order, and dependency wiring must be visible in code review
  • Multiple services should start from one canonical shape rather than each team's custom bootstrap
  • You use AI coding assistants and want machine-readable ownership rules: task-routing.yaml, change-recipes/, and module.yaml give agents a deterministic operating model
  • You need clear separation between stable modules and experimental capability work
  • You want a framework to hide structure decisions — Plumego keeps them explicit by design
  • Your team already has a large Gin/Echo codebase and the migration cost outweighs the benefit
  • You need a mature v1+ release with a proven multi-year track record
  • Your service is a small script or short-lived tool where structure overhead isn't worth it

When NOT to choose Plumego.

This section is required by Plumego's own content rules. An honest comparison includes cases where the framework is the wrong choice — not just where it wins.

convention-first teams

You want the framework to manage structure for you

Plumego keeps route registration, middleware order, and dependency wiring explicitly in your code. If your team prefers a framework that manages structure invisibly, the explicit model adds friction without adding value. Gin or Echo will be more productive.

existing large codebase

Your team already has a large Gin/Echo service

Migrating a mature Gin or Echo service to Plumego requires rewriting all handlers from custom context to net/http signatures. If your service is working and the team is productive, that cost is rarely worth it. Stay on what you have.

maximum throughput

Raw HTTP throughput is your primary bottleneck

Plumego uses a net/http-compatible trie router. Its overhead is minimal but non-zero compared to Fiber's Fasthttp baseline. If you have measured evidence that HTTP routing is your bottleneck (not database, not business logic), Fiber is the right conversation.

long production track record required

You need a proven v1+ track record

Plumego has a v1.0.0 tag and the 9 stable roots have a compatibility promise, but the project has not yet accumulated the long-term production track record that Gin or Echo have. If "proven at scale for 5+ years" is a hard requirement, Gin or Echo is the safer organizational choice.

A note on performance benchmarks.

Benchmark comparisons between these frameworks are widely published. Read them with care.

fiber caveat

Fiber's performance lead comes from Fasthttp, not from better routing

Fasthttp avoids standard library allocations by reusing buffers. This yields measurable throughput gains in synthetic benchmarks. However, those gains disappear if you need net/http middleware, use standard library HTTP clients against the same service, or run workloads where the bottleneck is database I/O rather than HTTP parsing.

plumego baseline

Plumego's router overhead is knowable and bounded

The trie router has no reflection and no global state. Sequential benchmarks show Plumego at 4,291 ns/op versus Chi's 4,547 ns/op on GET /users/:id. Gin and Echo achieve lower sequential numbers (~3,750 ns/op) but their parallel throughput advantage disappears when the bottleneck shifts to I/O. See the benchmark table below for the full picture.

general advice

Profile your actual workload before framework selection on performance grounds

In most Go HTTP services, the bottleneck is database round-trips, external API calls, or serialization — not the router. Framework selection on performance grounds alone is rarely justified without measurements on your specific workload at your target scale.

Migration cost to Plumego.

The decision to switch frameworks is rarely just about API ergonomics — it also involves handler rewrites, middleware replacements, and testing infrastructure. This table gives an honest estimate of the effort involved when migrating an existing service to Plumego.

From Cost What changes
Gin medium All handlers must be rewritten from gin.Context to func(w, r). Middleware must be adapted or replaced. Route syntax maps cleanly (/foo/:id stays the same). Expect 1–3 days per service depending on handler count.
Echo medium Handler rewrite from echo.Context + error return to func(w, r). Grouped routing concepts map cleanly. Middleware adapters needed. Expect similar effort to Gin migration — roughly 1–3 days per service.
Fiber high Full rewrite required. Fasthttp and net/http are fundamentally incompatible — no handler reuse, no middleware reuse, no testing infrastructure reuse. Observability, auth, and tracing libraries all need replacing. Expect weeks, not days.
Chi low Handler signature is identical (stdlib func(w, r)). Route syntax differs slightly: Chi uses /foo/{id} while Plumego uses /foo/:id. Middleware is fully compatible. Most services migrate in hours, not days.

Migration cost assumes a working service with existing handlers and middleware. A greenfield service starting from reference/standard-service has no migration cost.

Router performance benchmarks.

Sequential and parallel dispatch numbers measured with go test -bench -benchmem -count=3. Route: GET /users/:id. No middleware. linux/amd64, Intel Xeon @ 2.10 GHz. Includes full httptest request/response cycle. Relative column vs Plumego (1.0×).

Router overhead is rarely the real bottleneck — a database call adds 500 µs–5 ms; the router adds 1–5 µs. Choose based on what matters to your team.

Plumego vs Go stdlib baseline (http.ServeMux + PathValue())

Overhead comes from per-request context injection (480 B, 5 allocs), parameter map construction, and middleware chain wiring. For a handler spending 1 ms on I/O, router cost is under 0.1% of total request time.

Scenario stdlib ns/op Plumego ns/op Ratio
Static route 110 367 3.3×
Single :param + read 186 807 4.3×
Four :param deep path 722 1036 1.4×
70-route mixed table 247 660 2.7×

The four-param deep-path scenario (1.4×) shows overhead converges as path complexity increases — the trie traversal amortises over more work per request. Reproduce: go test -bench=BenchmarkRouter -benchmem -count=3 ./reference/benchmark/...

Sequential dispatch (ns/op)

Framework ns/op allocs/op B/op vs Plumego
Plumego this 4291 16 5715 1.0×
Chi 4547 17 6036 1.06×
Gin faster 3773 13 5331 0.88×
Echo faster 3743 13 5331 0.87×

Parallel throughput — b.RunParallel at GOMAXPROCS

Framework ns/op allocs/op B/op vs Plumego
Plumego this 663 7 592 1.0×
Chi 1013 8 912 1.53×
Gin faster 285 4 208 0.43×
Echo faster 231 4 208 0.35×

Among stdlib-compatible frameworks (Chi and Plumego), Plumego is 35% faster under concurrent load (663 vs 1,013 ns/op). Gin and Echo are ~2.5–3× faster than Plumego in this test because sync.Pool context eliminates per-request allocation — at the cost of a custom context type incompatible with stdlib middleware. If stdlib compatibility matters to your team, the relevant comparison is Chi, not Gin or Echo.

Reproduce: cd reference/benchmark && go test -bench=. -benchmem -count=3 ./...

Made your decision?

If Plumego matches your team's model, start from the reference app. If another toolkit is the better fit, that's the right call — this page exists to help you decide, not to sell.

Not sure it fits? Read the honest trade-off assessment →