Skip to content

Coming from Echo

Echo and Kruda share similar design philosophies — both are minimal, high-performance, and use error-returning handlers.

Quick Comparison

ConceptEchoKruda
Create appecho.New()kruda.New()
Contextecho.Context (interface)*kruda.Ctx (concrete)
Routee.GET("/path", handler)app.Get("/path", handler)
JSON responsec.JSON(200, obj)c.JSON(obj)
Status codec.JSON(404, obj)c.Status(404).JSON(obj)
Query paramc.QueryParam("key")c.Query("key")
Path paramc.Param("id")c.Param("id")
Bindc.Bind(&obj)Typed handler C[T]
Middlewaree.Use(middleware)app.Use(middleware)
Groupe.Group("/api")app.Group("/api")
Starte.Start(":3000")app.Listen(":3000")

Hello World

Echo:

go
package main

import (
    "net/http"
    "github.com/labstack/echo/v4"
)

func main() {
    e := echo.New()
    e.GET("/", func(c echo.Context) error {
        return c.JSON(http.StatusOK, map[string]string{
            "message": "hello",
        })
    })
    e.Start(":3000")
}

Kruda:

go
package main

import "github.com/go-kruda/kruda"

func main() {
    app := kruda.New()
    app.Get("/", func(c *kruda.Ctx) error {
        return c.JSON(kruda.Map{"message": "hello"})
    })
    app.Listen(":3000")
}

Key differences: no http.StatusOK needed (200 is default), concrete *Ctx instead of interface.

Key Differences

1. Concrete Context vs Interface

Echo uses echo.Context interface — powerful for mocking but adds overhead:

go
// Echo — interface dispatch on every method call
func handler(c echo.Context) error {
    return c.JSON(200, data)
}

Kruda uses concrete *kruda.Ctx — direct method calls, zero interface overhead:

go
// Kruda — direct method calls, inlinable
func handler(c *kruda.Ctx) error {
    return c.JSON(data)
}

2. Typed Handlers Replace Bind

Echo:

go
type CreateUser struct {
    Name  string `json:"name" validate:"required"`
    Email string `json:"email" validate:"required,email"`
}

e.POST("/users", func(c echo.Context) error {
    var input CreateUser
    if err := c.Bind(&input); err != nil {
        return echo.NewHTTPError(422, err.Error())
    }
    if err := c.Validate(input); err != nil {
        return err
    }
    return c.JSON(201, createUser(input))
})

// Plus: register validator globally
e.Validator = &CustomValidator{validator: validator.New()}

Kruda:

go
type CreateUser struct {
    Name  string `json:"name" validate:"required"`
    Email string `json:"email" validate:"required,email"`
}

app.Post("/users", func(c *kruda.Ctx) error {
    var input CreateUser
    if err := c.Bind(&input); err != nil {
        return err
    }
    return c.Status(201).JSON(createUser(input))
})
// Or use typed handlers for compile-time safety:
// kruda.Post[CreateUser, User](app, "/users", handler)

No separate bind + validate steps. No global validator registration. Validation is built into the type system.

3. Pluggable Transport

Echo uses net/http exclusively. Kruda offers both:

go
// Wing (default on Linux) — epoll+eventfd, 846K req/s
app := kruda.New()

// fasthttp — broad compatibility
app := kruda.New(kruda.FastHTTP())

// net/http — same as Echo, but with Kruda's ergonomics
app := kruda.New(kruda.NetHTTP())

4. Error Handling

Echo:

go
// Custom HTTP error handler
e.HTTPErrorHandler = func(err error, c echo.Context) {
    he, ok := err.(*echo.HTTPError)
    if ok {
        c.JSON(he.Code, he.Message)
    } else {
        c.JSON(500, map[string]string{"error": err.Error()})
    }
}

Kruda:

go
// Error mapping — cleaner, supports domain errors
app.MapError(ErrNotFound, 404, "resource not found")
app.MapError(ErrUnauthorized, 401, "unauthorized")

// Or custom handler
app.OnError(func(c *kruda.Ctx, err error) {
    // full control over error responses
})

5. Route Groups with Guards

Echo:

go
api := e.Group("/api/v1")
api.Use(jwtMiddleware)
api.GET("/users", listUsers)
api.POST("/users", createUser)

Kruda:

go
app.Group("/api/v1").
    Guard(jwtMiddleware).
    Get("/users", listUsers).
    Post("/users", createUser).
    Done()

Middleware Migration

EchoKruda
middleware.Logger()middleware.Logger() (built-in)
middleware.Recover()middleware.Recovery() (built-in)
middleware.CORS()middleware.CORS() (built-in)
middleware.RequestID()middleware.RequestID() (built-in)
middleware.TimeoutWithConfig()middleware.Timeout() (built-in)
middleware.RateLimiter()ratelimit.New() (contrib)
middleware.GzipWithConfig()compress.New() (contrib)
echojwt.WithConfig()jwt.New() (contrib)

What You Gain

  • 846K req/s — Wing transport (epoll+eventfd) by default on Linux
  • Type-safe handlers — no more bind + validate dance
  • Concrete context — no interface overhead, better inlining
  • Pluggable transport — Wing, fasthttp, or net/http, auto-selected
  • Auto OpenAPI — generated from C[T] types
  • Auto CRUDkruda.Resource[Product, string](app, "/products", service) generates full REST
  • Built-in DI — optional, no codegen needed

Released under the MIT License.